/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "../idlib/precompiled.h"
#pragma hdrstop

#include "tr_local.h"
#include "../sys/win32/win_local.h"

/*

strictly experimental / research codepaths

!!!if we use front facing occluders, we can portal flow from light centers

try depth_component_16 rendering

do we care about portals from light perspective? back / front face issues.

how do we do weapon depth hacks with shadow buffers?
	distort their world space vertexes instead of offsetting their depth?

jittering off the side of a projection will give wrong shadows

really huge lights, like sunlight, are going to be problematic with fixed projections
	we could tile the projections and let the auto-resize cut them down as necessary

It sucks that depth buffers are non-linear, because the bias and compares change with distance

polygon offset factor causes occasional texture holes from highly angled textures

*/

static	bool		initialized;

static	int lightBufferSize = 1024;
static	int	maxLightBufferSize = 1024;
static float lightBufferSizeFraction = 0.5;

static	int viewBufferSize = 1024;
static	int	viewBufferHeight = 768;
static	int	maxViewBufferSize = 1024;
static float viewBufferSizeFraction = 0.5;
static float viewBufferHeightFraction = 0.5;
static	bool	nativeViewBuffer = false;		// true if viewBufferSize is the viewport width

static	HPBUFFERARB	floatPbuffer;
static	HDC			floatPbufferDC;
static	idImage		*floatPbufferImage;

static	HPBUFFERARB	floatPbuffer2;
static	HDC			floatPbuffer2DC;
static	idImage		*floatPbuffer2Image;

static	HPBUFFERARB	floatPbufferQuarter;
static	HDC			floatPbufferQuarterDC;
static	idImage		*floatPbufferQuarterImage;

static	HGLRC		floatContext;

static	HPBUFFERARB	shadowPbuffer;
static	HDC			shadowPbufferDC;

static	HPBUFFERARB	viewPbuffer;
static	HDC			viewPbufferDC;

static	idImage		*shadowImage[3];

static	idImage		*viewDepthImage;
static	idImage		*viewAlphaImage;

static	idImage		*viewShadowImage;

static	idImage		*jitterImage16;
static	idImage		*jitterImage4;
static	idImage		*jitterImage1;

static	idImage		*random256Image;

static	int			shadowVertexProgram;
static	int			shadowFragmentProgram16;
static	int			shadowFragmentProgram4;
static	int			shadowFragmentProgram1;
static	int			shadowFragmentProgram0;

static	int			screenSpaceShadowVertexProgram;
static	int			screenSpaceShadowFragmentProgram16;
static	int			screenSpaceShadowFragmentProgram4;
static	int			screenSpaceShadowFragmentProgram1;
static	int			screenSpaceShadowFragmentProgram0;

static	int			depthMidpointVertexProgram;
static	int			depthMidpointFragmentProgram;

static	int			shadowResampleVertexProgram;
static	int			shadowResampleFragmentProgram;

static	int			gammaDitherVertexProgram;
static	int			gammaDitherFragmentProgram;

static	int			downSampleVertexProgram;
static	int			downSampleFragmentProgram;

static	int			bloomVertexProgram;
static	int			bloomFragmentProgram;

static	float		viewLightAxialSize;

idCVar r_sb_lightResolution( "r_sb_lightResolution", "1024", CVAR_RENDERER | CVAR_INTEGER, "Pixel dimensions for each shadow buffer, 64 - 2048" );
idCVar r_sb_viewResolution( "r_sb_viewResolution", "1024", CVAR_RENDERER | CVAR_INTEGER, "Width of screen space shadow sampling" );
idCVar r_sb_noShadows( "r_sb_noShadows", "0", CVAR_RENDERER | CVAR_BOOL, "don't draw any occluders" );
idCVar r_sb_usePbuffer( "r_sb_usePbuffer", "1", CVAR_RENDERER | CVAR_BOOL, "draw offscreen" );
idCVar r_sb_jitterScale( "r_sb_jitterScale", "0.006", CVAR_RENDERER | CVAR_FLOAT, "scale factor for jitter offset" );
idCVar r_sb_biasScale( "r_sb_biasScale", "0.0001", CVAR_RENDERER | CVAR_FLOAT, "scale factor for jitter bias" );
idCVar r_sb_samples( "r_sb_samples", "4", CVAR_RENDERER | CVAR_INTEGER, "0, 1, 4, or 16" );
idCVar r_sb_randomize( "r_sb_randomize", "1", CVAR_RENDERER | CVAR_BOOL, "randomly offset jitter texture each draw" );
// polyOfsFactor causes holes in low res images
idCVar r_sb_polyOfsFactor( "r_sb_polyOfsFactor", "2", CVAR_RENDERER | CVAR_FLOAT, "polygonOffset factor for drawing shadow buffer" );
idCVar r_sb_polyOfsUnits( "r_sb_polyOfsUnits", "3000", CVAR_RENDERER | CVAR_FLOAT, "polygonOffset units for drawing shadow buffer" );
idCVar r_sb_occluderFacing( "r_sb_occluderFacing", "0", CVAR_RENDERER | CVAR_INTEGER, "0 = front faces, 1 = back faces, 2 = midway between" );
// r_sb_randomizeBufferOrientation?

idCVar r_sb_frustomFOV( "r_sb_frustomFOV", "92", CVAR_RENDERER | CVAR_FLOAT, "oversize FOV for point light side matching" );
idCVar r_sb_showFrustumPixels( "r_sb_showFrustumPixels", "0", CVAR_RENDERER | CVAR_BOOL, "color the pixels contained in the frustum" );
idCVar r_sb_singleSide( "r_sb_singleSide", "-1", CVAR_RENDERER | CVAR_INTEGER, "only draw a single side (0-5) of point lights" );
idCVar r_sb_useCulling( "r_sb_useCulling", "1", CVAR_RENDERER | CVAR_BOOL, "cull geometry to individual side frustums" );
idCVar r_sb_linearFilter( "r_sb_linearFilter", "1", CVAR_RENDERER | CVAR_BOOL, "use GL_LINEAR instead of GL_NEAREST on shadow maps" );

idCVar r_sb_screenSpaceShadow( "r_sb_screenSpaceShadow", "1", CVAR_RENDERER | CVAR_BOOL, "build shadows in screen space instead of on surfaces" );

idCVar r_hdr_useFloats( "r_hdr_useFloats", "0", CVAR_RENDERER | CVAR_BOOL, "use a floating point rendering buffer" );
idCVar r_hdr_exposure( "r_hdr_exposure", "1.0", CVAR_RENDERER | CVAR_FLOAT, "maximum light scale" );
idCVar r_hdr_bloomFraction( "r_hdr_bloomFraction", "0.1", CVAR_RENDERER | CVAR_FLOAT, "fraction to smear across neighbors" );
idCVar r_hdr_gamma( "r_hdr_gamma", "1", CVAR_RENDERER | CVAR_FLOAT, "monitor gamma power" );
idCVar r_hdr_monitorDither( "r_hdr_monitorDither", "0.01", CVAR_RENDERER | CVAR_FLOAT, "random dither in monitor space" );

// from world space to light origin, looking down the X axis
static float	unflippedLightMatrix[16];

// from world space to OpenGL view space, looking down the negative Z axis
static float	lightMatrix[16];

// from OpenGL view space to OpenGL NDC ( -1 : 1 in XYZ )
static float	lightProjectionMatrix[16];


void	RB_ARB2_DrawInteraction( const drawInteraction_t *din );

typedef struct {
	const char	*name;
	int			num;
} wglString_t;

wglString_t	wglString[] = {
{ "WGL_NUMBER_PIXEL_FORMATS_ARB",		0x2000 },
{ "WGL_DRAW_TO_WINDOW_ARB",			0x2001 },
{ "WGL_DRAW_TO_BITMAP_ARB",			0x2002 },
{ "WGL_ACCELERATION_ARB",			0x2003 },
{ "WGL_NEED_PALETTE_ARB",			0x2004 },
{ "WGL_NEED_SYSTEM_PALETTE_ARB",		0x2005 },
{ "WGL_SWAP_LAYER_BUFFERS_ARB",		0x2006 },
{ "WGL_SWAP_METHOD_ARB",			0x2007 },
{ "WGL_NUMBER_OVERLAYS_ARB",			0x2008 },
{ "WGL_NUMBER_UNDERLAYS_ARB",		0x2009 },
{ "WGL_TRANSPARENT_ARB",			0x200A },
{ "WGL_TRANSPARENT_RED_VALUE_ARB",		0x2037 },
{ "WGL_TRANSPARENT_GREEN_VALUE_ARB",		0x2038 },
{ "WGL_TRANSPARENT_BLUE_VALUE_ARB",		0x2039 },
{ "WGL_TRANSPARENT_ALPHA_VALUE_ARB",		0x203A },
{ "WGL_TRANSPARENT_INDEX_VALUE_ARB",		0x203B },
{ "WGL_SHARE_DEPTH_ARB",			0x200C },
{ "WGL_SHARE_STENCIL_ARB",			0x200D },
{ "WGL_SHARE_ACCUM_ARB",			0x200E },
{ "WGL_SUPPORT_GDI_ARB",			0x200F },
{ "WGL_SUPPORT_OPENGL_ARB",			0x2010 },
{ "WGL_DOUBLE_BUFFER_ARB",			0x2011 },
{ "WGL_STEREO_ARB",				0x2012 },
{ "WGL_PIXEL_TYPE_ARB",			0x2013 },
{ "WGL_COLOR_BITS_ARB",			0x2014 },
{ "WGL_RED_BITS_ARB",			0x2015 },
{ "WGL_RED_SHIFT_ARB",			0x2016 },
{ "WGL_GREEN_BITS_ARB",			0x2017 },
{ "WGL_GREEN_SHIFT_ARB",			0x2018 },
{ "WGL_BLUE_BITS_ARB",			0x2019 },
{ "WGL_BLUE_SHIFT_ARB",			0x201A },
{ "WGL_ALPHA_BITS_ARB",			0x201B },
{ "WGL_ALPHA_SHIFT_ARB",			0x201C },
{ "WGL_ACCUM_BITS_ARB",			0x201D },
{ "WGL_ACCUM_RED_BITS_ARB",			0x201E },
{ "WGL_ACCUM_GREEN_BITS_ARB",		0x201F },
{ "WGL_ACCUM_BLUE_BITS_ARB",			0x2020 },
{ "WGL_ACCUM_ALPHA_BITS_ARB",		0x2021 },
{ "WGL_DEPTH_BITS_ARB",			0x2022 },
{ "WGL_STENCIL_BITS_ARB",			0x2023 },
{ "WGL_AUX_BUFFERS_ARB",			0x2024 },

{ "WGL_NO_ACCELERATION_ARB",			0x2025 },
{ "WGL_GENERIC_ACCELERATION_ARB",		0x2026 },
{ "WGL_FULL_ACCELERATION_ARB",		0x2027 },

{ "WGL_SWAP_EXCHANGE_ARB",			0x2028 },
{ "WGL_SWAP_COPY_ARB",			0x2029 },
{ "WGL_SWAP_UNDEFINED_ARB",			0x202A },

{ "WGL_TYPE_RGBA_ARB",			0x202B },
{ "WGL_TYPE_COLORINDEX_ARB",			0x202C },
};

static const int NUM_WGL_STRINGS = sizeof( wglString ) / sizeof( wglString[0] );

static void R_CheckWglErrors( void ) {
	int	err = GetLastError();
	char	*name;

#if 0
	LPVOID lpMsgBuf;
	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					err,
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
					(LPTSTR) &lpMsgBuf,
					0,
					NULL
					);
#endif
	err &= 0xffff;
	switch ( err ) {
	case 13: name = "ERROR_INVALID_DATA"; break;
	case 6: name = "ERROR_INVALID_HANDLE"; break;
	case 4317: name = "ERROR_INVALID_OPERATION"; break;
	default: name = va( "code %i", err ); break;
	}

	common->Printf( "GetLastError: %s\n", name );
}

static void R_MakeCurrent( HDC dc, HGLRC context, HPBUFFERARB pbuffer ) {
	if ( pbuffer ) {
		if ( !wglReleaseTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
			R_CheckWglErrors();
			common->Error( "wglReleaseTexImageARB failed" );
		}
	}
	if ( !qwglMakeCurrent( dc, context ) ) {
		R_CheckWglErrors();
		common->FatalError( "qwglMakeCurrent failed" );
	}
}

static void R_BindTexImage( HPBUFFERARB pbuffer ) {
	if ( !wglReleaseTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
		R_CheckWglErrors();
		common->Error( "wglReleaseTexImageARB failed" );
	}
	if ( !wglBindTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
		R_CheckWglErrors();
		common->Error( "failed wglBindTexImageARB" );
	}
}

static void R_ReportTextureParms( void ) {
	int	parms[8];

//	q glGetTexParameteriv( GL_TEXTURE_RECTANGLE_NV,
	qglGetIntegerv( GL_TEXTURE_BINDING_RECTANGLE_NV, parms );

}

/*
====================
RB_CreateBloomTable
====================
*/
static const int	BLOOM_RADIUS = 8;
static void RB_CreateBloomTable( void ) {
	float	bloom[BLOOM_RADIUS];
	float	total = 0;

	// gaussian
	float	stdDev = 2.0;
	for ( int i = 0 ; i < BLOOM_RADIUS ; i++ ) {
		float	f = (float)i / stdDev;
		bloom[i] = exp( -0.5 * f * f );
		total += bloom[i];
	}

	total = ( total - bloom[0] ) * 2 + bloom[0];

	// normalize to 1.0 contribution, so a full row or column will equal 1.0
	for ( int i = 0 ; i < BLOOM_RADIUS ; i++ ) {
		bloom[i] *= 1.0 / total;
		common->Printf( "PARAM bloom%i = { %f };\n", i, bloom[i] );
	}

}

/*
====================
GL_SelectTextureNoClient
====================
*/
static void GL_SelectTextureNoClient( int unit ) {
	backEnd.glState.currenttmu = unit;
	qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
	RB_LogComment( "glActiveTextureARB( %i )\n", unit );
}


/*
================
R_CreateShadowBufferImage

================
*/
static void R_CreateShadowBufferImage( idImage *image ) {
	byte	*data = (byte *)Mem_Alloc( lightBufferSize*lightBufferSize );

	memset( data, 0, lightBufferSize*lightBufferSize );

	image->GenerateImage( (byte *)data, 4, 4,
		TF_LINEAR, false, TR_CLAMP_TO_BORDER, TD_HIGH_QUALITY );

	// now reset it to a shadow depth image
	GL_CheckErrors();
	image->uploadWidth = image->uploadHeight = lightBufferSize;
	qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, lightBufferSize, lightBufferSize,
		0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, data );

	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE );
//	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL );

	// explicit zero depth border
	float	color[4];
	color[0] = color[1] = color[2] = color[3] = 0;
	qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color );

	GL_CheckErrors();

	Mem_Free( data );
}

static void R_CreateViewAlphaImage( idImage *image ) {
	int		c = viewBufferSize*viewBufferSize*4;
	byte	*data = (byte *)Mem_Alloc( c );

	// don't let it pick an intensity format
	for ( int i = 0 ; i < c ; i++ ) {
		data[i] = i;
	}
	memset( data, 0, viewBufferSize*viewBufferSize );

	image->GenerateImage( (byte *)data, viewBufferSize, viewBufferSize,
		TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY );
}

static void R_CreateStubImage( idImage *image ) {
	float	data[3][4][4];

	// generate the texture number
	qglGenTextures( 1, &image->texnum );
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, image->texnum );
	memset( data, 0, sizeof( data ) );
	glTexImage2D( GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_RGBA16_NV, 4, 3, 0, GL_RGBA, GL_FLOAT, &data );
}

/*
================
R_CreateJitterImage

================
*/
const static	int JITTER_SIZE = 128;
static void R_CreateJitterImage16( idImage *image ) {
	byte	data[JITTER_SIZE][JITTER_SIZE*16][4];

	for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
		for ( int s = 0 ; s < 16 ; s++ ) {
			int sOfs = 64 * ( s & 3 );
			int tOfs = 64 * ( ( s >> 2 ) & 3 );

			for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
				data[i][s*JITTER_SIZE+j][0] = (rand() & 63 ) | sOfs;
				data[i][s*JITTER_SIZE+j][1] = (rand() & 63 ) | tOfs;
				data[i][s*JITTER_SIZE+j][2] = rand();
				data[i][s*JITTER_SIZE+j][3] = 0;
			}
		}
	}

	image->GenerateImage( (byte *)data, JITTER_SIZE*16, JITTER_SIZE,
		TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}

static void R_CreateJitterImage4( idImage *image ) {
	byte	data[JITTER_SIZE][JITTER_SIZE*4][4];

	for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
		for ( int s = 0 ; s < 4 ; s++ ) {
			int sOfs = 128 * ( s & 1 );
			int tOfs = 128 * ( ( s >> 1 ) & 1 );

			for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
				data[i][s*JITTER_SIZE+j][0] = (rand() & 127 ) | sOfs;
				data[i][s*JITTER_SIZE+j][1] = (rand() & 127 ) | tOfs;
				data[i][s*JITTER_SIZE+j][2] = rand();
				data[i][s*JITTER_SIZE+j][3] = 0;
			}
		}
	}

	image->GenerateImage( (byte *)data, JITTER_SIZE*4, JITTER_SIZE,
		TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}

static void R_CreateJitterImage1( idImage *image ) {
	byte	data[JITTER_SIZE][JITTER_SIZE][4];

	for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
		for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
			data[i][j][0] = rand();
			data[i][j][1] = rand();
			data[i][j][2] = rand();
			data[i][j][3] = 0;
		}
	}

	image->GenerateImage( (byte *)data, JITTER_SIZE, JITTER_SIZE,
		TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}

static void R_CreateRandom256Image( idImage *image ) {
	byte	data[256][256][4];

	for ( int i = 0 ; i < 256 ; i++ ) {
		for ( int j = 0 ; j < 256 ; j++ ) {
			data[i][j][0] = rand();
			data[i][j][1] = rand();
			data[i][j][2] = rand();
			data[i][j][3] = rand();
		}
	}

	image->GenerateImage( (byte *)data, 256, 256,
		TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}


/*
==================
R_PrintPixelFormat
==================
*/
void R_PrintPixelFormat( int pixelFormat ) {
	int		res;
	int		iAttribute;
	int	iValue;

	common->Printf( "----- pixelFormat %i -----\n", pixelFormat );

	for ( int i = 1 ; i < NUM_WGL_STRINGS ; i++ ) {
		iAttribute = wglString[i].num;
		res = wglGetPixelFormatAttribivARB( win32.hDC, pixelFormat, 0, 1, &iAttribute, &iValue );
		if ( res && iValue ) {
			common->Printf( "%s : %i\n", wglString[i].name, iValue );
		}
	}
}


/*
==================
R_Exp_Allocate
==================
*/
void R_Exp_Allocate( void ) {
	// find a pixel format for our floating point pbuffer
	int		iAttributes[NUM_WGL_STRINGS*2], *atr_p;
	FLOAT	fAttributes[] = {0, 0};
	UINT	numFormats;
	int		pixelformats[1024];
	int		ret;
	int	pbiAttributes[] = {0, 0};

	initialized = true;

#if 1
	//
	// allocate the floating point rendering buffer
	//
	atr_p = iAttributes;

	*atr_p++ = WGL_DRAW_TO_PBUFFER_ARB;
	*atr_p++ = TRUE;
	*atr_p++ = WGL_FLOAT_COMPONENTS_NV;
	*atr_p++ = TRUE;
	*atr_p++ = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV;
	*atr_p++ = TRUE;
//	*atr_p++ = WGL_BIND_TO_TEXTURE_RGBA_ARB;
//	*atr_p++ = TRUE;
	*atr_p++ = WGL_DEPTH_BITS_ARB;
	*atr_p++ = 24;
	*atr_p++ = WGL_STENCIL_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = 0;
	*atr_p++ = 0;

	ret = wglChoosePixelFormatARB( win32.hDC, iAttributes, fAttributes,
		sizeof( pixelformats ) / sizeof( pixelformats[0] ), pixelformats, &numFormats );

#if 0
	for ( int i = 0 ; i < (int)numFormats ; i++ ) {
		R_PrintPixelFormat( pixelformats[i] );
	}
#endif
	common->Printf( "\nfloatPbuffer:\n" );
	R_PrintPixelFormat( pixelformats[0] );

	// allocate a pbuffer with this pixel format
	int	pbiAttributesTexture[] = {
		WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_FLOAT_RGBA_NV,
		WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_RECTANGLE_NV, // WGL_TEXTURE_2D_ARB,
			0, 0};

	floatPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth,
		glConfig.vidHeight, pbiAttributesTexture );
	if ( !floatPbuffer ) {
		common->Printf( "failed to create floatPbuffer.\n" );
		GL_CheckErrors();
	}
	floatPbufferDC = wglGetPbufferDCARB( floatPbuffer );
	floatPbufferImage = globalImages->ImageFromFunction( "_floatPbuffer", R_CreateStubImage );

	// create a second buffer for ping-pong operations
	floatPbuffer2 = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth,
		glConfig.vidHeight, pbiAttributesTexture );
	if ( !floatPbuffer2 ) {
		common->Printf( "failed to create floatPbuffer.\n" );
		GL_CheckErrors();
	}
	floatPbuffer2DC = wglGetPbufferDCARB( floatPbuffer2 );
	floatPbuffer2Image = globalImages->ImageFromFunction( "_floatPbuffer2", R_CreateStubImage );

	// create a third buffer for down sampling operations
	floatPbufferQuarter = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth / 4,
		glConfig.vidHeight / 4, pbiAttributesTexture );
	if ( !floatPbufferQuarter ) {
		common->Printf( "failed to create floatPbuffer.\n" );
		GL_CheckErrors();
	}
	floatPbufferQuarterDC = wglGetPbufferDCARB( floatPbufferQuarter );
	floatPbufferQuarterImage = globalImages->ImageFromFunction( "floatPbufferQuarter", R_CreateStubImage );

	// create a new GL context for this pixel format and share textures
	floatContext = wglCreateContext( floatPbufferDC );
	if ( !floatContext ) {
		common->Printf( "failed to create context for floatPbufferDC.\n" );
		GL_CheckErrors();
	}

	if ( !wglShareLists( floatContext, win32.hGLRC ) ) {
		common->Printf( "failed to share lists.\n" );
	}

	// create a rendering context for this pixel format and share textures

	// allocate a texture for the rendering

#endif

	//=================================================================================

	//
	// allocate the shadow pbuffer
	//
	atr_p = iAttributes;

	*atr_p++ = WGL_DRAW_TO_PBUFFER_ARB;
	*atr_p++ = TRUE;
	*atr_p++ = WGL_RED_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = WGL_GREEN_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = WGL_BLUE_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = WGL_ALPHA_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = WGL_DEPTH_BITS_ARB;
	*atr_p++ = 24;
	*atr_p++ = WGL_STENCIL_BITS_ARB;
	*atr_p++ = 8;
	*atr_p++ = 0;
	*atr_p++ = 0;

	ret = wglChoosePixelFormatARB( win32.hDC, iAttributes, fAttributes,
		sizeof( pixelformats ) / sizeof( pixelformats[0] ), pixelformats, &numFormats );
#if 0
	for ( int i = 0 ; i < (int)numFormats ; i++ ) {
		R_PrintPixelFormat( pixelformats[i] );
	}
#endif
	common->Printf( "\nshadowPbuffer:\n" );
	R_PrintPixelFormat( pixelformats[0] );

pixelformats[0] = win32.pixelformat;	// forced to do this by wgl...

	//-----------------------------------

	lightBufferSize = maxLightBufferSize;

	// allocate a pbuffer with this pixel format
	shadowPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], lightBufferSize,
		lightBufferSize, pbiAttributes );

	// allocate a rendering context for the pbuffer
	shadowPbufferDC = wglGetPbufferDCARB( shadowPbuffer );

	// generate the texture number
	shadowImage[0] = globalImages->ImageFromFunction( va("_shadowBuffer%i_0",lightBufferSize), R_CreateShadowBufferImage );
	shadowImage[1] = globalImages->ImageFromFunction( va("_shadowBuffer%i_1",lightBufferSize), R_CreateShadowBufferImage );
	shadowImage[2] = globalImages->ImageFromFunction( va("_shadowBuffer%i_2",lightBufferSize), R_CreateShadowBufferImage );

	//-----------------------------------

	lightBufferSize = maxViewBufferSize;

	// allocate a pbuffer with this pixel format
	viewPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], maxViewBufferSize,
		maxViewBufferSize, pbiAttributes );

	// allocate a rendering context for the pbuffer
	viewPbufferDC = wglGetPbufferDCARB( viewPbuffer );

	// create the image space depth buffer for image-space shadow trnasforms
	viewDepthImage = globalImages->ImageFromFunction("_viewDepth", R_CreateShadowBufferImage );

	// create the image space shadow alpha buffer for subsampling the shadow calculation
	viewAlphaImage = globalImages->ImageFromFunction("_viewAlpha", R_CreateViewAlphaImage );

	//-----------------------------------

	// generate the jitter image
	jitterImage16 = globalImages->ImageFromFunction( "_jitter16", R_CreateJitterImage16 );
	jitterImage4 = globalImages->ImageFromFunction( "_jitter4", R_CreateJitterImage4 );
	jitterImage1 = globalImages->ImageFromFunction( "_jitter1", R_CreateJitterImage1 );

	depthMidpointVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "depthMidpoint.vfp" );
	depthMidpointFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "depthMidpoint.vfp" );

	shadowResampleVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "shadowResample.vfp" );
	shadowResampleFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowResample.vfp" );

	screenSpaceShadowVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "screenSpaceShadow1.vfp" );

	screenSpaceShadowFragmentProgram0 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow0.vfp" );
	screenSpaceShadowFragmentProgram1 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow1.vfp" );
	screenSpaceShadowFragmentProgram4 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow4.vfp" );
	screenSpaceShadowFragmentProgram16 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow16.vfp" );

	shadowVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "shadowBufferInteraction1.vfp" );

	shadowFragmentProgram0 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction0.vfp" );
	shadowFragmentProgram1 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction1.vfp" );
	shadowFragmentProgram4 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction4.vfp" );
	shadowFragmentProgram16 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction16.vfp" );

	gammaDitherVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "gammaDither.vfp" );
	gammaDitherFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "gammaDither.vfp" );

	downSampleVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "downSample.vfp" );
	downSampleFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "downSample.vfp" );

	bloomVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "bloom.vfp" );
	bloomFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "bloom.vfp" );

	random256Image = globalImages->ImageFromFunction( "_random256", R_CreateRandom256Image );
}

//===========================================================================================

static const int CULL_RECEIVER = 1;	// still draw occluder, but it is out of the view
static const int CULL_OCCLUDER_AND_RECEIVER = 2;	// the surface doesn't effect the view at all

/*
==================
RB_EXP_CullInteractions

Sets surfaceInteraction_t->cullBits
==================
*/
void RB_EXP_CullInteractions( viewLight_t *vLight, idPlane frustumPlanes[6] ) {
	for ( idInteraction *inter = vLight->lightDef->firstInteraction ; inter ; inter = inter->lightNext ) {
		const idRenderEntityLocal *entityDef = inter->entityDef;
		if ( !entityDef ) {
			continue;
		}
		if ( inter->numSurfaces < 1 ) {
			continue;
		}

		int	culled = 0;

		if ( r_sb_useCulling.GetBool() ) {
			// transform light frustum into object space, positive side points outside the light
			idPlane	localPlanes[6];
			int		plane;
			for ( plane = 0 ; plane < 6 ; plane++ ) {
				R_GlobalPlaneToLocal( entityDef->modelMatrix, frustumPlanes[plane], localPlanes[plane] );
			}

			// cull the entire entity bounding box
			// has referenceBounds been tightened to the actual model bounds?
			idVec3	corners[8];
			for ( int i = 0 ; i < 8 ; i++ ) {
				corners[i][0] = entityDef->referenceBounds[i&1][0];
				corners[i][1] = entityDef->referenceBounds[(i>>1)&1][1];
				corners[i][2] = entityDef->referenceBounds[(i>>2)&1][2];
			}

			for ( plane = 0 ; plane < 6 ; plane++ ) {
				int		j;
				for ( j = 0 ; j < 8 ; j++ ) {
					// if a corner is on the negative side (inside) of the frustum, the surface is not culled
					// by this plane
					if ( corners[j] * localPlanes[plane].ToVec4().ToVec3() + localPlanes[plane][3] < 0 ) {
						break;
					}
				}
				if ( j == 8 ) {
					break;			// all points outside the light
				}
			}
			if ( plane < 6 ) {
				culled = CULL_OCCLUDER_AND_RECEIVER;
			}
		}

		for ( int i = 0 ; i < inter->numSurfaces ; i++ ) {
			surfaceInteraction_t	*surfInt = &inter->surfaces[i];

			if ( !surfInt->ambientTris ) {
				continue;
			}
			surfInt->expCulled = culled;
		}

	}
}

/*
==================
RB_EXP_RenderOccluders
==================
*/
void RB_EXP_RenderOccluders( viewLight_t *vLight ) {
	for ( idInteraction *inter = vLight->lightDef->firstInteraction ; inter ; inter = inter->lightNext ) {
		const idRenderEntityLocal *entityDef = inter->entityDef;
		if ( !entityDef ) {
			continue;
		}
		if ( inter->numSurfaces < 1 ) {
			continue;
		}

		// no need to check for current on this, because each interaction is always
		// a different space
		float	matrix[16];
		myGlMultMatrix( inter->entityDef->modelMatrix, lightMatrix, matrix );
		qglLoadMatrixf( matrix );

		// draw each surface
		for ( int i = 0 ; i < inter->numSurfaces ; i++ ) {
			surfaceInteraction_t	*surfInt = &inter->surfaces[i];

			if ( !surfInt->ambientTris ) {
				continue;
			}
			if ( surfInt->shader && !surfInt->shader->SurfaceCastsShadow() ) {
				continue;
			}

			// cull it
			if ( surfInt->expCulled == CULL_OCCLUDER_AND_RECEIVER ) {
				continue;
			}

			// render it
			const srfTriangles_t *tri = surfInt->ambientTris;
			if ( !tri->ambientCache ) {
				R_CreateAmbientCache( const_cast<srfTriangles_t *>(tri), false );
			}
			idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
			qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );
	qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() );
	if ( surfInt->shader ) {
		surfInt->shader->GetEditorImage()->Bind();
	}
			RB_DrawElementsWithCounters( tri );
		}
	}
}

/*
==================
RB_RenderShadowBuffer
==================
*/
void    RB_RenderShadowBuffer( viewLight_t	*vLight, int side ) {
	float	xmin, xmax, ymin, ymax;
	float	width, height;
	float	zNear;

	float	fov = r_sb_frustomFOV.GetFloat();

	//
	// set up 90 degree projection matrix
	//
	zNear	= 4;

	ymax = zNear * tan( fov * idMath::PI / 360.0f );
	ymin = -ymax;

	xmax = zNear * tan( fov * idMath::PI / 360.0f );
	xmin = -xmax;

	width = xmax - xmin;
	height = ymax - ymin;

	lightProjectionMatrix[0] = 2 * zNear / width;
	lightProjectionMatrix[4] = 0;
	lightProjectionMatrix[8] = 0;
	lightProjectionMatrix[12] = 0;

	lightProjectionMatrix[1] = 0;
	lightProjectionMatrix[5] = 2 * zNear / height;
	lightProjectionMatrix[9] = 0;
	lightProjectionMatrix[13] = 0;

	// this is the far-plane-at-infinity formulation, and
	// crunches the Z range slightly so w=0 vertexes do not
	// rasterize right at the wraparound point
	lightProjectionMatrix[2] = 0;
	lightProjectionMatrix[6] = 0;
	lightProjectionMatrix[10] = -0.999f;
	lightProjectionMatrix[14] = -2.0f * zNear;

	lightProjectionMatrix[3] = 0;
	lightProjectionMatrix[7] = 0;
	lightProjectionMatrix[11] = -1;
	lightProjectionMatrix[15] = 0;


	if ( r_sb_usePbuffer.GetBool() ) {
		// set the current openGL drawable to the shadow buffer
		R_MakeCurrent( shadowPbufferDC, win32.hGLRC, NULL /* !@# shadowPbuffer */ );
	}

	qglMatrixMode( GL_PROJECTION );
	qglLoadMatrixf( lightProjectionMatrix );
	qglMatrixMode( GL_MODELVIEW );

	qglViewport( 0, 0, lightBufferSize, lightBufferSize );
	qglScissor( 0, 0, lightBufferSize, lightBufferSize );
	qglStencilFunc( GL_ALWAYS, 0, 255 );

qglClearColor( 0, 1, 0, 0 );
GL_State( GLS_DEPTHFUNC_LESS | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );	// make sure depth mask is off before clear
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

// draw all the occluders
qglColor3f( 1, 1, 1 );
GL_SelectTexture( 0 );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );

	backEnd.currentSpace = NULL;

	static float	s_flipMatrix[16] = {
		// convert from our coordinate system (looking down X)
		// to OpenGL's coordinate system (looking down -Z)
		0, 0, -1, 0,
		-1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 0, 1
	};

	float	viewMatrix[16];

	idVec3	vec;
	idVec3	origin = vLight->lightDef->globalLightOrigin;

	if ( side == -1 ) {
		// projected light
		vec = vLight->lightDef->parms.target;
		vec.Normalize();
		viewMatrix[0] = vec[0];
		viewMatrix[4] = vec[1];
		viewMatrix[8] = vec[2];

		vec = vLight->lightDef->parms.right;
		vec.Normalize();
		viewMatrix[1] = -vec[0];
		viewMatrix[5] = -vec[1];
		viewMatrix[9] = -vec[2];

		vec = vLight->lightDef->parms.up;
		vec.Normalize();
		viewMatrix[2] = vec[0];
		viewMatrix[6] = vec[1];
		viewMatrix[10] = vec[2];
	} else {
		// side of a point light
		memset( viewMatrix, 0, sizeof( viewMatrix ) );
		switch ( side ) {
		case 0:
			viewMatrix[0] = 1;
			viewMatrix[9] = 1;
			viewMatrix[6] = -1;
			break;
		case 1:
			viewMatrix[0] = -1;
			viewMatrix[9] = -1;
			viewMatrix[6] = -1;
			break;
		case 2:
			viewMatrix[4] = 1;
			viewMatrix[1] = -1;
			viewMatrix[10] = 1;
			break;
		case 3:
			viewMatrix[4] = -1;
			viewMatrix[1] = -1;
			viewMatrix[10] = -1;
			break;
		case 4:
			viewMatrix[8] = 1;
			viewMatrix[1] = -1;
			viewMatrix[6] = -1;
			break;
		case 5:
			viewMatrix[8] = -1;
			viewMatrix[1] = 1;
			viewMatrix[6] = -1;
			break;
		}
	}

	viewMatrix[12] = -origin[0] * viewMatrix[0] + -origin[1] * viewMatrix[4] + -origin[2] * viewMatrix[8];
	viewMatrix[13] = -origin[0] * viewMatrix[1] + -origin[1] * viewMatrix[5] + -origin[2] * viewMatrix[9];
	viewMatrix[14] = -origin[0] * viewMatrix[2] + -origin[1] * viewMatrix[6] + -origin[2] * viewMatrix[10];

	viewMatrix[3] = 0;
	viewMatrix[7] = 0;
	viewMatrix[11] = 0;
	viewMatrix[15] = 1;

	memcpy( unflippedLightMatrix, viewMatrix, sizeof( unflippedLightMatrix ) );
	myGlMultMatrix( viewMatrix, s_flipMatrix,lightMatrix);

	// create frustum planes
	idPlane	globalFrustum[6];

	// near clip
	globalFrustum[0][0] = -viewMatrix[0];
	globalFrustum[0][1] = -viewMatrix[4];
	globalFrustum[0][2] = -viewMatrix[8];
	globalFrustum[0][3] = -(origin[0] * globalFrustum[0][0] + origin[1] * globalFrustum[0][1] + origin[2] * globalFrustum[0][2]);

	// far clip
	globalFrustum[1][0] = viewMatrix[0];
	globalFrustum[1][1] = viewMatrix[4];
	globalFrustum[1][2] = viewMatrix[8];
	globalFrustum[1][3] = -globalFrustum[0][3] - viewLightAxialSize;

	// side clips
	globalFrustum[2][0] = -viewMatrix[0] + viewMatrix[1];
	globalFrustum[2][1] = -viewMatrix[4] + viewMatrix[5];
	globalFrustum[2][2] = -viewMatrix[8] + viewMatrix[9];

	globalFrustum[3][0] = -viewMatrix[0] - viewMatrix[1];
	globalFrustum[3][1] = -viewMatrix[4] - viewMatrix[5];
	globalFrustum[3][2] = -viewMatrix[8] - viewMatrix[9];

	globalFrustum[4][0] = -viewMatrix[0] + viewMatrix[2];
	globalFrustum[4][1] = -viewMatrix[4] + viewMatrix[6];
	globalFrustum[4][2] = -viewMatrix[8] + viewMatrix[10];

	globalFrustum[5][0] = -viewMatrix[0] - viewMatrix[2];
	globalFrustum[5][1] = -viewMatrix[4] - viewMatrix[6];
	globalFrustum[5][2] = -viewMatrix[8] - viewMatrix[10];

	// is this nromalization necessary?
	for ( int i = 0 ; i < 6 ; i++ ) {
		globalFrustum[i].ToVec4().ToVec3().Normalize();
	}

	for ( int i = 2 ; i < 6 ; i++ ) {
		globalFrustum[i][3] = - (origin * globalFrustum[i].ToVec4().ToVec3() );
	}

	RB_EXP_CullInteractions( vLight, globalFrustum );


	// FIXME: we want to skip the sampling as well as the generation when not casting shadows
	if ( !r_sb_noShadows.GetBool() && vLight->lightShader->LightCastsShadows() ) {
		//
		// set polygon offset for the rendering
		//
		switch ( r_sb_occluderFacing.GetInteger() ) {
		case 0:		// front sides
			qglPolygonOffset( r_sb_polyOfsFactor.GetFloat(), r_sb_polyOfsUnits.GetFloat() );
			qglEnable( GL_POLYGON_OFFSET_FILL );
			RB_EXP_RenderOccluders( vLight );
			qglDisable( GL_POLYGON_OFFSET_FILL );
			break;
		case 1:		// back sides
			qglPolygonOffset( -r_sb_polyOfsFactor.GetFloat(), -r_sb_polyOfsUnits.GetFloat() );
			qglEnable( GL_POLYGON_OFFSET_FILL );
			GL_Cull( CT_BACK_SIDED );
			RB_EXP_RenderOccluders( vLight );
			GL_Cull( CT_FRONT_SIDED );
			qglDisable( GL_POLYGON_OFFSET_FILL );
			break;
		case 2:		// both sides
			GL_Cull( CT_BACK_SIDED );
			RB_EXP_RenderOccluders( vLight );
			GL_Cull( CT_FRONT_SIDED );
			shadowImage[2]->Bind();
			qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );

			RB_EXP_RenderOccluders( vLight );
			shadowImage[1]->Bind();
			qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );

			// fragment program to combine the two depth images
			qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, depthMidpointVertexProgram );
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, depthMidpointFragmentProgram );
			qglEnable(GL_VERTEX_PROGRAM_ARB);
			qglEnable(GL_FRAGMENT_PROGRAM_ARB);

			GL_SelectTextureNoClient( 1 );
			shadowImage[1]->Bind();
			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

			GL_SelectTextureNoClient( 0 );
			shadowImage[2]->Bind();
			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

			// draw a full screen quad
			qglMatrixMode( GL_PROJECTION );
			qglLoadIdentity();
			qglOrtho( 0, 1, 0, 1, -1, 1 );
			qglMatrixMode( GL_MODELVIEW );
			qglLoadIdentity();

			GL_State( GLS_DEPTHFUNC_ALWAYS );

			qglBegin( GL_TRIANGLE_FAN );
			qglTexCoord2f( 0, 0 );
			qglVertex2f( 0, 0 );
			qglTexCoord2f( 0, lightBufferSizeFraction );
			qglVertex2f( 0, 1 );
			qglTexCoord2f( lightBufferSizeFraction, lightBufferSizeFraction );
			qglVertex2f( 1, 1 );
			qglTexCoord2f( lightBufferSizeFraction, 0 );
			qglVertex2f( 1, 0 );
			qglEnd();

			qglDisable( GL_VERTEX_PROGRAM_ARB );
			qglDisable( GL_FRAGMENT_PROGRAM_ARB );

			break;
		}
	}

	// copy to the texture
	shadowImage[0]->Bind();
	qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );

qglDisableClientState( GL_TEXTURE_COORD_ARRAY );


	// reset the normal view matrix

	qglMatrixMode( GL_PROJECTION );
	qglLoadMatrixf( backEnd.viewDef->projectionMatrix );
	qglMatrixMode( GL_MODELVIEW );

	// the current modelView matrix is not valid
	backEnd.currentSpace = NULL;
}

/*
==================
RB_EXP_DrawInteraction
==================
*/
void	RB_EXP_DrawInteraction( const drawInteraction_t *din ) {
	// load all the vertex program parameters
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() );


// calculate depth projection for shadow buffer
float	sRow[4];
float	tRow[4];
float	rRow[4];
float	qRow[4];
float	matrix[16];
float	matrix2[16];
myGlMultMatrix( din->surf->space->modelMatrix, lightMatrix, matrix );
myGlMultMatrix( matrix, lightProjectionMatrix, matrix2 );

// the final values need to be in 0.0 : 1.0 range instead of -1 : 1
sRow[0] = 0.5 * lightBufferSizeFraction * ( matrix2[0] + matrix2[3] );
sRow[1] = 0.5 * lightBufferSizeFraction * ( matrix2[4] + matrix2[7] );
sRow[2] = 0.5 * lightBufferSizeFraction * ( matrix2[8] + matrix2[11] );
sRow[3] = 0.5 * lightBufferSizeFraction * ( matrix2[12] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 18, sRow );
tRow[0] = 0.5 * lightBufferSizeFraction * ( matrix2[1] + matrix2[3] );
tRow[1] = 0.5 * lightBufferSizeFraction * ( matrix2[5] + matrix2[7] );
tRow[2] = 0.5 * lightBufferSizeFraction * ( matrix2[9] + matrix2[11] );
tRow[3] = 0.5 * lightBufferSizeFraction * ( matrix2[13] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 19, tRow );
rRow[0] = 0.5 * ( matrix2[2] + matrix2[3] );
rRow[1] = 0.5 * ( matrix2[6] + matrix2[7] );
rRow[2] = 0.5 * ( matrix2[10] + matrix2[11] );
rRow[3] = 0.5 * ( matrix2[14] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 20, rRow );
qRow[0] = matrix2[3];
qRow[1] = matrix2[7];
qRow[2] = matrix2[11];
qRow[3] = matrix2[15];
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 21, qRow );


	// testing fragment based normal mapping
	if ( r_testARBProgram.GetBool() ) {
		qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() );
		qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() );
	}

	static const float zero[4] = { 0, 0, 0, 0 };
	static const float one[4] = { 1, 1, 1, 1 };
	static const float negOne[4] = { -1, -1, -1, -1 };

	switch ( din->vertexColor ) {
	case SVC_IGNORE:
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero );
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one );
		break;
	case SVC_MODULATE:
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one );
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero );
		break;
	case SVC_INVERSE_MODULATE:
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne );
		qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one );
		break;
	}

	// set the constant colors
	qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() );
	qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() );

	//-----------------------------------------------------
	// screen power of two correction factor

	float	parm[4];
	parm[0] = 1.0 / ( JITTER_SIZE * r_sb_samples.GetInteger() ) ;
	parm[1] = 1.0 / JITTER_SIZE;
	parm[2] = 0;
	parm[3] = 1;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );

	// jitter tex scale
	parm[0] =
	parm[1] = r_sb_jitterScale.GetFloat() * lightBufferSizeFraction;
	parm[2] = -r_sb_biasScale.GetFloat();
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, parm );

	// jitter tex offset
	if ( r_sb_randomize.GetBool() ) {
		parm[0] = (rand()&255) / 255.0;
		parm[1] = (rand()&255) / 255.0;
	} else {
		parm[0] = parm[1] = 0;
	}
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );
	//-----------------------------------------------------

	// set the textures

	// texture 1 will be the per-surface bump map
	GL_SelectTextureNoClient( 1 );
	din->bumpImage->Bind();

	// texture 2 will be the light falloff texture
	GL_SelectTextureNoClient( 2 );
	din->lightFalloffImage->Bind();

	// texture 3 will be the light projection texture
	GL_SelectTextureNoClient( 3 );
	din->lightImage->Bind();

	// texture 4 is the per-surface diffuse map
	GL_SelectTextureNoClient( 4 );
	din->diffuseImage->Bind();

	// texture 5 is the per-surface specular map
	GL_SelectTextureNoClient( 5 );
	din->specularImage->Bind();

	// draw it
	RB_DrawElementsWithCounters( din->surf->geo );
}

/*
=============
RB_EXP_CreateDrawInteractions

=============
*/
void RB_EXP_CreateDrawInteractions( const drawSurf_t *surf ) {
	if ( !surf ) {
		return;
	}
	if ( r_sb_screenSpaceShadow.GetBool() ) {
		// perform setup here that will be constant for all interactions
		GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc );

		if ( r_testARBProgram.GetBool() ) {
			qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_TEST );
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_TEST );
		} else {
			qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_INTERACTION );
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_INTERACTION );
		}
	} else {
		// perform setup here that will be constant for all interactions
		GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc );
GL_State( GLS_DEPTHMASK | GLS_DEPTHFUNC_ALWAYS );//!@#

		// bind the vertex program
		qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, shadowVertexProgram );
		if ( r_sb_samples.GetInteger() == 16 ) {
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram16 );
		} else if ( r_sb_samples.GetInteger() == 4 ) {
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram4 );
		} else if ( r_sb_samples.GetInteger() == 1 ) {
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram1 );
		} else {
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram0 );
		}
	}

	qglEnable(GL_VERTEX_PROGRAM_ARB);
	qglEnable(GL_FRAGMENT_PROGRAM_ARB);

	// enable the vertex arrays
	qglEnableVertexAttribArrayARB( 8 );
	qglEnableVertexAttribArrayARB( 9 );
	qglEnableVertexAttribArrayARB( 10 );
	qglEnableVertexAttribArrayARB( 11 );
	qglEnableClientState( GL_COLOR_ARRAY );

	// texture 0 is the normalization cube map for the vector towards the light
	GL_SelectTextureNoClient( 0 );
	if ( backEnd.vLight->lightShader->IsAmbientLight() ) {
		globalImages->normalCubeMapImage->Bind();
		qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_AMBIENT);
		qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_AMBIENT);
	} else {
		globalImages->normalCubeMapImage->Bind();
	}

	// texture 6 is the specular lookup table
	GL_SelectTextureNoClient( 6 );
	if ( r_testARBProgram.GetBool() ) {
		globalImages->specular2DTableImage->Bind();	// variable specularity in alpha channel
	} else {
		globalImages->specularTableImage->Bind();
	}


	for ( ; surf ; surf=surf->nextOnLight ) {
		// perform setup here that will not change over multiple interaction passes
		if ( backEnd.vLight->lightShader->IsAmbientLight() ) {
			float	parm[4];

			parm[0] = surf->space->modelMatrix[0];
			parm[1] = surf->space->modelMatrix[4];
			parm[2] = surf->space->modelMatrix[8];
			parm[3] = 0;
			qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 20, parm );
			parm[0] = surf->space->modelMatrix[1];
			parm[1] = surf->space->modelMatrix[5];
			parm[2] = surf->space->modelMatrix[9];
			parm[3] = 0;
			qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 21, parm );
			parm[0] = surf->space->modelMatrix[2];
			parm[1] = surf->space->modelMatrix[6];
			parm[2] = surf->space->modelMatrix[10];
			parm[3] = 0;
			qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 22, parm );

			GL_SelectTextureNoClient( 0 );
			const shaderStage_t *stage = backEnd.vLight->lightShader->GetStage( 0 );
			if ( stage->newStage ) {
				stage->newStage->fragmentProgramImages[7]->BindFragment();
			}
		}

		// set the vertex pointers
		idDrawVert	*ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache );
		qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color );
		qglVertexAttribPointerARB( 11, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->normal.ToFloatPtr() );
		qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() );
		qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() );
		qglVertexAttribPointerARB( 8, 2, GL_FLOAT, false, sizeof( idDrawVert ), ac->st.ToFloatPtr() );
		qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );

		// this may cause RB_ARB2_DrawInteraction to be exacuted multiple
		// times with different colors and images if the surface or light have multiple layers
		if ( r_sb_screenSpaceShadow.GetBool() ) {
			RB_CreateSingleDrawInteractions( surf, RB_ARB2_DrawInteraction );
		} else {
			RB_CreateSingleDrawInteractions( surf, RB_EXP_DrawInteraction );
		}
	}

	qglDisableVertexAttribArrayARB( 8 );
	qglDisableVertexAttribArrayARB( 9 );
	qglDisableVertexAttribArrayARB( 10 );
	qglDisableVertexAttribArrayARB( 11 );
	qglDisableClientState( GL_COLOR_ARRAY );

	// disable features
	GL_SelectTextureNoClient( 6 );
	globalImages->BindNull();

	GL_SelectTextureNoClient( 5 );
	globalImages->BindNull();

	GL_SelectTextureNoClient( 4 );
	globalImages->BindNull();

	GL_SelectTextureNoClient( 3 );
	globalImages->BindNull();

	GL_SelectTextureNoClient( 2 );
	globalImages->BindNull();

	GL_SelectTextureNoClient( 1 );
	globalImages->BindNull();

	backEnd.glState.currenttmu = -1;
	GL_SelectTexture( 0 );

	qglDisable(GL_VERTEX_PROGRAM_ARB);
	qglDisable(GL_FRAGMENT_PROGRAM_ARB);
}

void InvertByTranspose( const float a[16], float r[16] ) {
	r[ 0] = a[ 0];
	r[ 1] = a[ 4];
	r[ 2] = a[ 8];
	r[ 3] = 0;
	r[ 4] = a[ 1];
	r[ 5] = a[ 5];
	r[ 6] = a[ 9];
	r[ 7] = 0;
	r[ 8] = a[ 2];
	r[ 9] = a[ 6];
	r[10] = a[10];
	r[11] = 0;
	r[12] = -(r[ 0]*a[12] + r[ 4]*a[13] + r[ 8]*a[14]);
	r[13] = -(r[ 1]*a[12] + r[ 5]*a[13] + r[ 9]*a[14]);
	r[14] = -(r[ 2]*a[12] + r[ 6]*a[13] + r[10]*a[14]);
	r[15] = 1;
}

void FullInvert( const float a[16], float r[16] ) {
	idMat4	am;

	for ( int i = 0 ; i < 4 ; i++ ) {
		for ( int j = 0 ; j < 4 ; j++ ) {
			am[i][j] = a[j*4+i];
		}
	}

//	idVec4 test( 100, 100, 100, 1 );
//	idVec4	transformed, inverted;
//	transformed = test * am;

	if ( !am.InverseSelf() ) {
		common->Error( "Invert failed" );
	}
//	inverted = transformed * am;

	for ( int i = 0 ; i < 4 ; i++ ) {
		for ( int j = 0 ; j < 4 ; j++ ) {
			r[j*4+i] = am[i][j];
		}
	}
}

/*
==================
RB_Exp_TrianglesForFrustum
==================
*/
const srfTriangles_t	*RB_Exp_TrianglesForFrustum( viewLight_t *vLight, int side ) {
	const srfTriangles_t *tri;

	static srfTriangles_t	frustumTri;
	static idDrawVert		verts[5];
	static glIndex_t		indexes[18] = { 0, 1, 2,	0, 2, 3,	0, 3, 4,	0, 4, 1,	2, 1, 4,	2, 4, 3 };

	if ( side == -1 ) {
		tri = vLight->frustumTris;
	} else {
		memset( verts, 0, sizeof( verts ) );

		for ( int i = 0 ; i < 5 ; i++ ) {
			verts[i].xyz = vLight->globalLightOrigin;
		}

		memset( &frustumTri, 0, sizeof( frustumTri ) );
		frustumTri.indexes = indexes;
		frustumTri.verts = verts;
		frustumTri.numIndexes = 18;
		frustumTri.numVerts = 5;

		tri = &frustumTri;

		float	size = viewLightAxialSize;

		switch ( side ) {
		case 0:
			verts[1].xyz[0] += size;
			verts[2].xyz[0] += size;
			verts[3].xyz[0] += size;
			verts[4].xyz[0] += size;
			verts[1].xyz[1] += size;
			verts[1].xyz[2] += size;
			verts[2].xyz[1] -= size;
			verts[2].xyz[2] += size;
			verts[3].xyz[1] -= size;
			verts[3].xyz[2] -= size;
			verts[4].xyz[1] += size;
			verts[4].xyz[2] -= size;
			break;
		case 1:
			verts[1].xyz[0] -= size;
			verts[2].xyz[0] -= size;
			verts[3].xyz[0] -= size;
			verts[4].xyz[0] -= size;
			verts[1].xyz[1] -= size;
			verts[1].xyz[2] += size;
			verts[2].xyz[1] += size;
			verts[2].xyz[2] += size;
			verts[3].xyz[1] += size;
			verts[3].xyz[2] -= size;
			verts[4].xyz[1] -= size;
			verts[4].xyz[2] -= size;
			break;
		case 2:
			verts[1].xyz[1] += size;
			verts[2].xyz[1] += size;
			verts[3].xyz[1] += size;
			verts[4].xyz[1] += size;
			verts[1].xyz[0] -= size;
			verts[1].xyz[2] += size;
			verts[2].xyz[0] += size;
			verts[2].xyz[2] += size;
			verts[3].xyz[0] += size;
			verts[3].xyz[2] -= size;
			verts[4].xyz[0] -= size;
			verts[4].xyz[2] -= size;
			break;
		case 3:
			verts[1].xyz[1] -= size;
			verts[2].xyz[1] -= size;
			verts[3].xyz[1] -= size;
			verts[4].xyz[1] -= size;
			verts[1].xyz[0] += size;
			verts[1].xyz[2] += size;
			verts[2].xyz[0] -= size;
			verts[2].xyz[2] += size;
			verts[3].xyz[0] -= size;
			verts[3].xyz[2] -= size;
			verts[4].xyz[0] += size;
			verts[4].xyz[2] -= size;
			break;
		case 4:
			verts[1].xyz[2] += size;
			verts[2].xyz[2] += size;
			verts[3].xyz[2] += size;
			verts[4].xyz[2] += size;
			verts[1].xyz[0] += size;
			verts[1].xyz[1] += size;
			verts[2].xyz[0] -= size;
			verts[2].xyz[1] += size;
			verts[3].xyz[0] -= size;
			verts[3].xyz[1] -= size;
			verts[4].xyz[0] += size;
			verts[4].xyz[1] -= size;
			break;
		case 5:
			verts[1].xyz[2] -= size;
			verts[2].xyz[2] -= size;
			verts[3].xyz[2] -= size;
			verts[4].xyz[2] -= size;
			verts[1].xyz[0] -= size;
			verts[1].xyz[1] += size;
			verts[2].xyz[0] += size;
			verts[2].xyz[1] += size;
			verts[3].xyz[0] += size;
			verts[3].xyz[1] -= size;
			verts[4].xyz[0] -= size;
			verts[4].xyz[1] -= size;
			break;
		}

		frustumTri.ambientCache = vertexCache.AllocFrameTemp( verts, sizeof( verts ) );
	}

	return tri;
}


/*
==================
RB_Exp_SelectFrustum
==================
*/
void	RB_Exp_SelectFrustum( viewLight_t *vLight, int side ) {
	qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix );

	const srfTriangles_t *tri = RB_Exp_TrianglesForFrustum( vLight, side );

	idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
	qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );

	qglDisable( GL_TEXTURE_2D );
	qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
	// clear stencil buffer
	qglEnable( GL_SCISSOR_TEST );
	qglEnable( GL_STENCIL_TEST );
	qglClearStencil( 1 );
	qglClear( GL_STENCIL_BUFFER_BIT );

	// draw front faces of the light frustum, incrementing the stencil buffer on depth fail
	// so we can't draw on those pixels
	GL_State( GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
	qglStencilFunc( GL_ALWAYS, 0, 255 );
	qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP );
	GL_Cull( CT_FRONT_SIDED );

	RB_DrawElementsWithCounters( tri );

	// draw back faces of the light frustum with
	// depth test greater
	// stencil test of equal 1
	// zero stencil stencil when depth test passes, so subsequent surface drawing
	// can occur on those pixels

	// this pass does all the shadow filtering
	qglStencilFunc( GL_EQUAL, 1, 255 );
	qglStencilOp( GL_KEEP, GL_KEEP, GL_ZERO );

	GL_Cull( CT_BACK_SIDED );
	qglDepthFunc( GL_GREATER );

	// write to destination alpha
	if ( r_sb_showFrustumPixels.GetBool() ) {
		GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
		qglDisable( GL_TEXTURE_2D );
		qglColor4f( 0, 0.25, 0, 1 );
	} else {
		GL_State( GLS_COLORMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
		qglEnable(GL_VERTEX_PROGRAM_ARB);
		qglEnable(GL_FRAGMENT_PROGRAM_ARB);
		qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, screenSpaceShadowVertexProgram );
		switch ( r_sb_samples.GetInteger() ) {
		case 0:
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram0 );
			break;
		case 1:
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram1 );
			break;
		case 4:
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram4 );
			break;
		case 16:
			qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram16 );
			break;
		}
	}

/*
texture[0] = view depth texture
texture[1] = jitter texture
texture[2] = light depth texture
*/
	GL_SelectTextureNoClient( 2 );
	shadowImage[0]->Bind();

	GL_SelectTextureNoClient( 1 );
	if ( r_sb_samples.GetInteger() == 16 ) {
		jitterImage16->Bind();
	} else if ( r_sb_samples.GetInteger() == 4 ) {
		jitterImage4->Bind();
	} else {
		jitterImage1->Bind();
	}

	GL_SelectTextureNoClient( 0 );
	viewDepthImage->Bind();

	/*
PARAM	positionToDepthTexScale		= program.local[0];	# fragment.position to screen depth texture transformation
PARAM	zProject					= program.local[1];	# projection[10], projection[14], 0, 0
PARAM	positionToViewSpace			= program.local[2];	# X add, Y add, X mul, Y mul
PARAM	viewToLightS				= program.local[3];
PARAM	viewToLightT				= program.local[4];
PARAM	viewToLightR				= program.local[5];
PARAM	viewToLightQ				= program.local[6];
PARAM	positionToJitterTexScale	= program.local[7];	# fragment.position to jitter texture
PARAM	jitterTexScale				= program.local[8];
PARAM	jitterTexOffset				= program.local[9];
*/
	float	parm[4];
	int		pot;

	// calculate depth projection for shadow buffer
	float	sRow[4];
	float	tRow[4];
	float	rRow[4];
	float	qRow[4];
	float	invertedView[16];
	float	invertedProjection[16];
	float	matrix[16];
	float	matrix2[16];

	// we need the inverse of the projection matrix to go from NDC to view
	FullInvert( backEnd.viewDef->projectionMatrix, invertedProjection );

	/*
	from window to NDC:
		( x - xMid ) * 1.0 / xMid
		( y - yMid ) * 1.0 / yMid
		( z - 0.5 ) * 2

	from NDC to clip coordinates:
		rcp(1/w)

	*/

	// we need the inverse of the viewMatrix to go from view (looking down negative Z) to world
	InvertByTranspose( backEnd.viewDef->worldSpace.modelViewMatrix, invertedView );

	// then we go from world to light view space (looking down negative Z)
	myGlMultMatrix( invertedView, lightMatrix, matrix );

	// then to light projection, giving X/w, Y/w, Z/w in the -1 : 1 range
	myGlMultMatrix( matrix, lightProjectionMatrix, matrix2 );

	// the final values need to be in 0.0 : 1.0 range instead of -1 : 1
	sRow[0] = 0.5 * ( matrix2[0] + matrix2[3] ) * lightBufferSizeFraction;
	sRow[1] = 0.5 * ( matrix2[4] + matrix2[7] ) * lightBufferSizeFraction;
	sRow[2] = 0.5 * ( matrix2[8] + matrix2[11] ) * lightBufferSizeFraction;
	sRow[3] = 0.5 * ( matrix2[12] + matrix2[15] ) * lightBufferSizeFraction;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, sRow );
	tRow[0] = 0.5 * ( matrix2[1] + matrix2[3] ) * lightBufferSizeFraction;
	tRow[1] = 0.5 * ( matrix2[5] + matrix2[7] ) * lightBufferSizeFraction;
	tRow[2] = 0.5 * ( matrix2[9] + matrix2[11] ) * lightBufferSizeFraction;
	tRow[3] = 0.5 * ( matrix2[13] + matrix2[15] ) * lightBufferSizeFraction;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, tRow );
	rRow[0] = 0.5 * ( matrix2[2] + matrix2[3] );
	rRow[1] = 0.5 * ( matrix2[6] + matrix2[7] );
	rRow[2] = 0.5 * ( matrix2[10] + matrix2[11] );
	rRow[3] = 0.5 * ( matrix2[14] + matrix2[15] );
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 5, rRow );
	qRow[0] = matrix2[3];
	qRow[1] = matrix2[7];
	qRow[2] = matrix2[11];
	qRow[3] = matrix2[15];
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 6, qRow );

	//-----------------------------------------------------
	// these should be constant for the entire frame

	// convert 0..viewport-1 sizes to fractions inside the POT screen depth texture
	int	 w = viewBufferSize;
	pot = MakePowerOfTwo( w );
	parm[0] = 1.0 / maxViewBufferSize;	//  * ( (float)viewBufferSize / w );
	int	 h = viewBufferHeight;
	pot = MakePowerOfTwo( h );
	parm[1] = parm[0]; // 1.0 / pot;
	parm[2] = 0;
	parm[3] = 1;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );

	// zProject values
	parm[0] = backEnd.viewDef->projectionMatrix[10];
	parm[1] = backEnd.viewDef->projectionMatrix[14];
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm );

	// positionToViewSpace
	parm[0] = -1.0 / backEnd.viewDef->projectionMatrix[0];
	parm[1] = -1.0 / backEnd.viewDef->projectionMatrix[5];
	parm[2] = 2.0/viewBufferSize;
	parm[3] = 2.0/viewBufferSize;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );

	// positionToJitterTexScale
	parm[0] = 1.0 / ( JITTER_SIZE * r_sb_samples.GetInteger() ) ;
	parm[1] = 1.0 / JITTER_SIZE;
	parm[2] = 0;
	parm[3] = 1;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 7, parm );

	// jitter tex scale
	parm[0] =
	parm[1] = r_sb_jitterScale.GetFloat() * lightBufferSizeFraction;
	parm[2] = -r_sb_biasScale.GetFloat();
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 8, parm );

	// jitter tex offset
	if ( r_sb_randomize.GetBool() ) {
		parm[0] = (rand()&255) / 255.0;
		parm[1] = (rand()&255) / 255.0;
	} else {
		parm[0] = parm[1] = 0;
	}
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 9, parm );
	//-----------------------------------------------------



	RB_DrawElementsWithCounters( tri );

	qglDisable(GL_VERTEX_PROGRAM_ARB);
	qglDisable(GL_FRAGMENT_PROGRAM_ARB);

	GL_Cull( CT_FRONT_SIDED );
//	qglEnableClientState( GL_TEXTURE_COORD_ARRAY );

	qglDepthFunc( GL_LEQUAL );
	if ( r_sb_showFrustumPixels.GetBool() ) {
		qglEnable( GL_TEXTURE_2D );
		qglColor3f( 1, 1, 1 );
	}

	// after all the frustums have been drawn, the surfaces that have been drawn on will get interactions
	// scissor may still be a win even with the stencil test for very fast rejects
	qglStencilFunc( GL_EQUAL, 0, 255 );
	qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );

	// we can avoid clearing the stencil buffer by changing the hasLight value for each light
}

/*
==================
R_EXP_CalcLightAxialSize

all light side projections must currently match, so non-centered
and non-cubic lights must take the largest length
==================
*/
float	R_EXP_CalcLightAxialSize( viewLight_t *vLight ) {
	float	max = 0;

	if ( !vLight->lightDef->parms.pointLight ) {
		idVec3	dir = vLight->lightDef->parms.target - vLight->lightDef->parms.origin;
		max = dir.Length();
		return max;
	}

	for ( int i = 0 ; i < 3 ; i++ ) {
		float	dist = fabs(vLight->lightDef->parms.lightCenter[i] );
		dist += vLight->lightDef->parms.lightRadius[i];
		if ( dist > max ) {
			max = dist;
		}
	}
	return max;
}

/*
==================
R_EXP_RenderViewDepthImage

This could be avoided by drop sampling the native view depth buffer with render to texture
Bilerp might even be aprorpiate, although it would cause issues at edges
==================
*/
void RB_T_FillDepthBuffer( const drawSurf_t *surf );

void R_EXP_RenderViewDepthImage( void ) {
	if ( !r_sb_screenSpaceShadow.GetBool() ) {
		return;
	}

	// if the screen resolution is exactly the window width, we can
	// use the depth buffer we already have
	if ( 0 ) { // nativeViewBuffer ) {
		viewDepthImage->CopyDepthbuffer( backEnd.viewDef->viewport.x1,
			backEnd.viewDef->viewport.y1,  backEnd.viewDef->viewport.x2 -  backEnd.viewDef->viewport.x1 + 1,
			backEnd.viewDef->viewport.y2 -  backEnd.viewDef->viewport.y1 + 1 );
	} else {
		RB_LogComment( "---------- R_EXP_RenderViewDepthImage ----------\n" );

		if ( r_sb_usePbuffer.GetBool() ) {
			GL_CheckErrors();
			// set the current openGL drawable to the shadow buffer
			R_MakeCurrent( viewPbufferDC, win32.hGLRC, NULL /* !@# viewPbuffer */ );
		}

		// render the depth to the new size
		qglViewport( 0, 0, viewBufferSize, viewBufferHeight );
		qglScissor( 0, 0, viewBufferSize, viewBufferHeight );
		qglClear( GL_DEPTH_BUFFER_BIT );
		qglStencilFunc( GL_ALWAYS, 0, 255 );

		// the first texture will be used for alpha tested surfaces
		GL_SelectTexture( 0 );
		qglEnableClientState( GL_TEXTURE_COORD_ARRAY );

		GL_State( GLS_DEPTHFUNC_LESS );

		RB_RenderDrawSurfListWithFunction( backEnd.viewDef->drawSurfs, backEnd.viewDef->numDrawSurfs, RB_T_FillDepthBuffer );

		//
		// copy it to a texture
		//
		viewDepthImage->Bind();
		qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, viewBufferSize, viewBufferHeight );

		if ( r_sb_usePbuffer.GetBool() ) {
			// set the normal screen drawable current
			R_MakeCurrent( win32.hDC, win32.hGLRC, NULL );
		}

		// reset the window clipping
		qglMatrixMode( GL_PROJECTION );
		qglLoadMatrixf( backEnd.viewDef->projectionMatrix );
		qglMatrixMode( GL_MODELVIEW );

		qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
			tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
			backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
			backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );
		qglScissor( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
			tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
			backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
			backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );

		// the current modelView matrix is not valid
		backEnd.currentSpace = NULL;
	}

	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}

/*
==================
RB_EXP_SetNativeBuffer

This is always the back buffer, and scissor is set full screen
==================
*/
void RB_EXP_SetNativeBuffer( void ) {
	// set the normal screen drawable current
	R_MakeCurrent( win32.hDC, win32.hGLRC, NULL );

	qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
		tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
		backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
		backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );

	backEnd.currentScissor = backEnd.viewDef->viewport;
	if ( r_useScissor.GetBool() ) {
		qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
			backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
			backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
			backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 );
	}
}

/*
==================
RB_EXP_SetRenderBuffer

This may be to a float pBuffer, and scissor is set to cover only the light
==================
*/
void RB_EXP_SetRenderBuffer( viewLight_t *vLight ) {
	if ( r_hdr_useFloats.GetBool() ) {
		R_MakeCurrent( floatPbufferDC, floatContext, floatPbuffer );
	} else {
		if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) {
			GL_CheckErrors();
			common->FatalError( "Couldn't return to normal drawing context" );
		}
	}

	qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
		tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
		backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
		backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );

	if ( !vLight ) {
		backEnd.currentScissor = backEnd.viewDef->viewport;
	} else {
		backEnd.currentScissor = vLight->scissorRect;
	}
	if ( r_useScissor.GetBool() ) {
		qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
			backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
			backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
			backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 );
	}
}

/*
==================
RB_shadowResampleAlpha

==================
*/
void	RB_shadowResampleAlpha( void ) {
	viewAlphaImage->Bind();
	// we could make this a subimage, but it isn't relevent once we have render-to-texture
	qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, viewBufferSize, viewBufferHeight );

	RB_EXP_SetRenderBuffer( backEnd.vLight );

//=====================

	qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix );

	// this uses the full light, not side frustums
	const srfTriangles_t *tri = backEnd.vLight->frustumTris;

	idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
	qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );

	// clear stencil buffer
	qglEnable( GL_SCISSOR_TEST );
	qglEnable( GL_STENCIL_TEST );
	qglClearStencil( 1 );
	qglClear( GL_STENCIL_BUFFER_BIT );

	// draw front faces of the light frustum, incrementing the stencil buffer on depth fail
	// so we can't draw on those pixels
	GL_State( GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
	qglStencilFunc( GL_ALWAYS, 0, 255 );
	qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP );
	GL_Cull( CT_FRONT_SIDED );

	// set fragment / vertex program?

	RB_DrawElementsWithCounters( tri );

	// draw back faces of the light frustum with
	// depth test greater
	// stencil test of equal 1
	// zero stencil stencil when depth test passes, so subsequent interaction drawing
	// can occur on those pixels

	// this pass does all the shadow filtering
	qglStencilFunc( GL_EQUAL, 1, 255 );
	qglStencilOp( GL_KEEP, GL_KEEP, GL_ZERO );

	// write to destination alpha
	if ( r_sb_showFrustumPixels.GetBool() ) {
		GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
		qglDisable( GL_TEXTURE_2D );
		qglColor4f( 0, 0.25, 0, 1 );
	} else {
		GL_State( GLS_COLORMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
		qglEnable(GL_VERTEX_PROGRAM_ARB);
		qglEnable(GL_FRAGMENT_PROGRAM_ARB);
		qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, shadowResampleVertexProgram );
		qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowResampleFragmentProgram );

		// convert 0..viewport-1 sizes to fractions inside the POT screen depth texture
		// shrink by one unit for bilerp
		float	parm[4];
		parm[0] = 1.0 / (maxViewBufferSize+1) * viewBufferSize / maxViewBufferSize;
		parm[1] = parm[0];
		parm[2] = 0;
		parm[3] = 1;
		qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
	}

	GL_Cull( CT_BACK_SIDED );
	qglDepthFunc( GL_GREATER );

	RB_DrawElementsWithCounters( tri );

	qglDisable(GL_VERTEX_PROGRAM_ARB);
	qglDisable(GL_FRAGMENT_PROGRAM_ARB);

	GL_Cull( CT_FRONT_SIDED );

	qglDepthFunc( GL_LEQUAL );
	if ( r_sb_showFrustumPixels.GetBool() ) {
		qglEnable( GL_TEXTURE_2D );
		qglColor3f( 1, 1, 1 );
	}

	// after all the frustums have been drawn, the surfaces that have been drawn on will get interactions
	// scissor may still be a win even with the stencil test for very fast rejects
	qglStencilFunc( GL_EQUAL, 0, 255 );
	qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
}


/*
==================
RB_EXP_CoverScreen
==================
*/
void RB_EXP_CoverScreen( void ) {
	// draw a full screen quad
	qglMatrixMode( GL_PROJECTION );
	qglLoadIdentity();
	qglOrtho( 0, 1, 0, 1, -1, 1 );
	qglMatrixMode( GL_MODELVIEW );
	qglLoadIdentity();

	qglBegin( GL_TRIANGLE_FAN );
	qglVertex2f( 0, 0 );
	qglVertex2f( 0, 1 );
	qglVertex2f( 1, 1 );
	qglVertex2f( 1, 0 );
	qglEnd();
}

/*
==================
RB_EXP_ReadFloatBuffer
==================
*/
void RB_EXP_ReadFloatBuffer( void ) {
	int		pixels = glConfig.vidWidth * glConfig.vidHeight;
	float	*buf = (float *)R_StaticAlloc( pixels * 4 * sizeof( float ) );

	qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_FLOAT, buf );

	float	mins[4] = { 9999, 9999, 9999, 9999 };
	float	maxs[4] = { -9999, -9999, -9999, -9999 };
	for ( int i = 0 ; i < pixels ; i++ ) {
		for ( int j = 0 ; j < 4 ; j++ ) {
			float	v = buf[ i*4 + j ];
			if ( v < mins[j] ) {
				mins[j] = v;
			}
			if ( v > maxs[j] ) {
				maxs[j] = v;
			}
		}
	}

	RB_EXP_SetNativeBuffer();

	qglLoadIdentity();
	qglMatrixMode( GL_PROJECTION );
	GL_State( GLS_DEPTHFUNC_ALWAYS );
	qglColor3f( 1, 1, 1 );
	qglPushMatrix();
	qglLoadIdentity();
	qglDisable( GL_TEXTURE_2D );
	qglOrtho( 0, 1, 0, 1, -1, 1 );
	qglRasterPos2f( 0.01f, 0.01f );
	qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_FLOAT, buf );
	qglPopMatrix();
	qglEnable( GL_TEXTURE_2D );
	qglMatrixMode( GL_MODELVIEW );

	R_StaticFree( buf );
}


void RB_TestGamma( void );

/*
==================
RB_EXP_GammaDither
==================
*/
void	RB_EXP_GammaDither( void ) {
	if ( !r_hdr_useFloats.GetBool() ) {
		return;
	}

#if 0
r_testGamma.SetBool( true );
RB_TestGamma();
r_testGamma.SetBool( false );
#endif

	RB_EXP_SetNativeBuffer();

	/*
# texture 0 is the high dynamic range buffer
# texture 1 is the random dither texture
# texture 2 is the light bloom texture

# writes result.color as the 32 bit dithered and gamma corrected values

PARAM	exposure =			program.local[0];		# multiply HDR value by this to get screen pixels
PARAM	gammaPower =		program.local[1];
PARAM	monitorDither =		program.local[2];
PARAM	positionToDitherScale =		program.local[3];
PARAM	bloomFraction =		program.local[4];
PARAM	positionToBloomScale =		program.local[5];

	*/

	qglBindProgramARB( GL_VERTEX_PROGRAM_ARB,gammaDitherVertexProgram );
	qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, gammaDitherFragmentProgram );
	qglEnable(GL_VERTEX_PROGRAM_ARB);
	qglEnable(GL_FRAGMENT_PROGRAM_ARB);

	qglActiveTextureARB( GL_TEXTURE2_ARB );
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferQuarterImage->texnum );
	R_BindTexImage( floatPbufferQuarter );

	qglActiveTextureARB( GL_TEXTURE1_ARB );
	random256Image->BindFragment();

	qglActiveTextureARB( GL_TEXTURE0_ARB );
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferImage->texnum );
	R_BindTexImage( floatPbuffer );

	float	parm[4];

	parm[0] = r_hdr_exposure.GetFloat();
	parm[1] = 0;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );

	parm[0] = r_hdr_gamma.GetFloat();
	parm[1] = 0;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm );

	parm[0] = r_hdr_monitorDither.GetFloat();
	parm[1] = 0;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );

	parm[0] = 1.0 / 256;
	parm[1] = parm[0];
	parm[2] = rand()/65535.0;
	parm[3] = rand()/65535.0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, parm );

	parm[0] = 1.0 - r_hdr_bloomFraction.GetFloat();
	parm[1] = r_hdr_bloomFraction.GetFloat();
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );

	parm[0] = 0.25;
	parm[1] = 0.25;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 5, parm );

	qglDisable( GL_STENCIL_TEST );
	qglDisable( GL_SCISSOR_TEST );
	qglDisable( GL_DEPTH_TEST );

	RB_EXP_CoverScreen();

	qglEnable( GL_DEPTH_TEST );

	qglDisable(GL_VERTEX_PROGRAM_ARB);
	qglDisable(GL_FRAGMENT_PROGRAM_ARB);
}

/*
==================
RB_EXP_Bloom
==================
*/
void	RB_EXP_Bloom( void ) {
	if ( !r_hdr_useFloats.GetBool() ) {
		return;
	}

	if ( r_hdr_bloomFraction.GetFloat() == 0 ) {
		return;
	}

	GL_CheckErrors();

	//
	// mip map
	//

	// draw to the second floatPbuffer
	R_MakeCurrent( floatPbuffer2DC, floatContext, floatPbuffer2 );

	GL_State( 0 );
	qglDisable( GL_DEPTH_TEST );
	qglDisable( GL_SCISSOR_TEST );

	qglEnable(GL_VERTEX_PROGRAM_ARB);
	qglEnable(GL_FRAGMENT_PROGRAM_ARB);

	qglClearColor( 1.0, 0.5, 0, 0 );
	qglClear( GL_COLOR_BUFFER_BIT );
	qglViewport( 0, 0, glConfig.vidWidth>>1, glConfig.vidHeight>>1 );

	// read from the original floatPbuffer
	qglActiveTextureARB( GL_TEXTURE0_ARB );
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferImage->texnum );
	R_BindTexImage( floatPbuffer );

	qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, downSampleVertexProgram );
	qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, downSampleFragmentProgram );

	RB_EXP_CoverScreen();

	//
	// mip map again
	//
	qglViewport( 0, 0, glConfig.vidWidth>>2, glConfig.vidHeight>>2 );

	// draw to the second floatPbuffer
	R_MakeCurrent( floatPbufferQuarterDC, floatContext, floatPbufferQuarter );

	// read from the original floatPbuffer
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbuffer2Image->texnum );
	R_BindTexImage( floatPbuffer2 );

	RB_EXP_CoverScreen();

	//
	// blur horizontally
	//
	/*
# texture 0 is the high dynamic range buffer
# writes result.color as the fp16 result of a smeared bloom

PARAM	step =		program.local[0];		# { 1, 0 } or { 0, 1 } for horizontal / vertical separation
	*/

	// draw to the second floatPbuffer
	R_MakeCurrent( floatPbuffer2DC, floatContext, floatPbuffer2 );

	qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, bloomVertexProgram );
	qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, bloomFragmentProgram );
	qglEnable(GL_VERTEX_PROGRAM_ARB);
	qglEnable(GL_FRAGMENT_PROGRAM_ARB);

	GL_SelectTextureNoClient( 0 );

	// blur horizontally first to the second floatPbuffer
	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferQuarterImage->texnum );
	R_BindTexImage( floatPbufferQuarter );

	float	parm[4];

	parm[0] = 1;
	parm[1] = 0;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );

	RB_EXP_CoverScreen();

	//
	// now blur vertically back to the quarter pbuffer
	//
	R_MakeCurrent( floatPbufferQuarterDC, floatContext, floatPbufferQuarter );

	qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbuffer2Image->texnum );
	R_BindTexImage( floatPbuffer2 );

	parm[0] = 0;
	parm[1] = 1;
	parm[2] = 0;
	parm[3] = 0;
	qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );

	RB_EXP_CoverScreen();

	//========================

	qglEnable( GL_DEPTH_TEST );
	qglEnable( GL_SCISSOR_TEST );

	qglDisable(GL_VERTEX_PROGRAM_ARB);
	qglDisable(GL_FRAGMENT_PROGRAM_ARB);

	GL_CheckErrors();
}

/*
==================
RB_Exp_DrawInteractions
==================
*/
void    RB_Exp_DrawInteractions( void ) {
	if ( !initialized ) {
		R_Exp_Allocate();
	}

	if ( !backEnd.viewDef->viewLights ) {
		return;
	}

	// validate the samples
	if ( r_sb_samples.GetInteger() != 16 && r_sb_samples.GetInteger() != 4 && r_sb_samples.GetInteger() != 1 ) {
		r_sb_samples.SetInteger( 0 );
	}

	// validate the light resolution
	if ( r_sb_lightResolution.GetInteger() < 64 ) {
		r_sb_lightResolution.SetInteger( 64 );
	} else if ( r_sb_lightResolution.GetInteger() > maxLightBufferSize ) {
		r_sb_lightResolution.SetInteger( maxLightBufferSize );
	}
	lightBufferSize = r_sb_lightResolution.GetInteger();
	lightBufferSizeFraction = (float)lightBufferSize / maxLightBufferSize;

	// validate the view resolution
	if ( r_sb_viewResolution.GetInteger() < 64 ) {
		r_sb_viewResolution.SetInteger( 64 );
	} else if ( r_sb_viewResolution.GetInteger() > maxViewBufferSize ) {
		r_sb_viewResolution.SetInteger( maxViewBufferSize );
	}
	viewBufferSize = r_sb_viewResolution.GetInteger();
	viewBufferHeight = viewBufferSize * 3 / 4;
	viewBufferSizeFraction = (float)viewBufferSize / maxViewBufferSize;
	viewBufferHeightFraction = (float)viewBufferHeight / maxViewBufferSize;
	if (  viewBufferSize == backEnd.viewDef->viewport.x2 -  backEnd.viewDef->viewport.x1 + 1 ) {
		nativeViewBuffer = true;
	} else {
		nativeViewBuffer = false;
	}

	// set up for either point sampled or percentage-closer filtering for the shadow sampling
	shadowImage[0]->BindFragment();
	if ( r_sb_linearFilter.GetBool() ) {
		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	} else {
		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}
	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE );

	globalImages->BindNull();

	// copy the current depth buffer to a texture for image-space shadowing,
	// or re-render at a lower resolution
	R_EXP_RenderViewDepthImage();

	GL_SelectTexture( 0 );
	qglDisableClientState( GL_TEXTURE_COORD_ARRAY );

	// disable stencil shadow test
	qglStencilFunc( GL_ALWAYS, 128, 255 );

	// the jitter image will be used to offset sample centers
	GL_SelectTextureNoClient( 8 );
	if ( r_sb_samples.GetInteger() == 16 ) {
		jitterImage16->BindFragment();
	} else if ( r_sb_samples.GetInteger() == 4 ) {
		jitterImage4->BindFragment();
	} else {
		jitterImage1->BindFragment();
	}

	// if we are using a float buffer, clear it now
	if ( r_hdr_useFloats.GetBool() ) {
		RB_EXP_SetRenderBuffer( NULL );
		// we need to set a lot of things, because this is a completely different context
		RB_SetDefaultGLState();
		qglClearColor( 0.001f, 1.0f, 0.01f, 0.1f );
		qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
		// clear the z buffer, set the projection matrix, etc
		RB_BeginDrawingView();
		RB_STD_FillDepthBuffer( (drawSurf_t **)&backEnd.viewDef->drawSurfs[0], backEnd.viewDef->numDrawSurfs );
	}


	//
	// for each light, perform adding and shadowing
	//
	for ( viewLight_t *vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) {
		backEnd.vLight = vLight;

		const idMaterial	*lightShader = vLight->lightShader;

		// do fogging later
		if ( lightShader->IsFogLight() ) {
			continue;
		}
		if ( lightShader->IsBlendLight() ) {
			continue;
		}

		if ( !vLight->localInteractions && !vLight->globalInteractions
			&& !vLight->translucentInteractions ) {
			continue;
		}

		if ( !vLight->frustumTris->ambientCache ) {
			R_CreateAmbientCache( const_cast<srfTriangles_t *>(vLight->frustumTris), false );
		}

		// all light side projections must currently match, so non-centered
		// and non-cubic lights must take the largest length
		viewLightAxialSize = R_EXP_CalcLightAxialSize( vLight );

		int	side, sideStop;

		if ( vLight->lightDef->parms.pointLight ) {
			if ( r_sb_singleSide.GetInteger() != -1 ) {
				side = r_sb_singleSide.GetInteger();
				sideStop = side+1;
			} else {
				side = 0;
				sideStop = 6;
			}
		} else {
			side = -1;
			sideStop = 0;
		}

		for (  ; side < sideStop ; side++ ) {
			// FIXME: check for frustums completely off the screen

			// render a shadow buffer
			RB_RenderShadowBuffer( vLight, side );

			// back to view rendering, possibly in the off-screen buffer
			if ( nativeViewBuffer || !r_sb_screenSpaceShadow.GetBool() ) {
				// directly to screen
				RB_EXP_SetRenderBuffer( vLight );
			} else {
				// to off screen buffer
				if ( r_sb_usePbuffer.GetBool() ) {
					GL_CheckErrors();
					// set the current openGL drawable to the shadow buffer
					R_MakeCurrent( viewPbufferDC, win32.hGLRC, viewPbuffer );
				}
				qglViewport( 0, 0, viewBufferSize, viewBufferHeight );
				qglScissor( 0, 0, viewBufferSize, viewBufferHeight );	// !@# FIXME: scale light scissor
			}

			// render the shadows into destination alpha on the included pixels
			RB_Exp_SelectFrustum( vLight, side );

			if ( !r_sb_screenSpaceShadow.GetBool() ) {
				// bind shadow buffer to texture
				GL_SelectTextureNoClient( 7 );
				shadowImage[0]->BindFragment();

				RB_EXP_CreateDrawInteractions( vLight->localInteractions );
				RB_EXP_CreateDrawInteractions( vLight->globalInteractions );
				backEnd.depthFunc = GLS_DEPTHFUNC_LESS;
				RB_EXP_CreateDrawInteractions( vLight->translucentInteractions );
				backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL;
			}
		}

		// render the native window coordinates interactions
		if ( r_sb_screenSpaceShadow.GetBool() ) {
			if ( !nativeViewBuffer ) {
				RB_shadowResampleAlpha();
				qglEnable( GL_STENCIL_TEST );
			} else {
				RB_EXP_SetRenderBuffer( vLight );
if ( r_ignore.GetBool() ) {
qglEnable( GL_STENCIL_TEST );	//!@#
} else {
qglDisable( GL_STENCIL_TEST );	//!@#
}
			}
			RB_EXP_CreateDrawInteractions( vLight->localInteractions );
			RB_EXP_CreateDrawInteractions( vLight->globalInteractions );
			backEnd.depthFunc = GLS_DEPTHFUNC_LESS;
			RB_EXP_CreateDrawInteractions( vLight->translucentInteractions );
			backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL;
		}
	}

	GL_SelectTexture( 0 );
	qglEnableClientState( GL_TEXTURE_COORD_ARRAY );

	// experimental transfer function
	for ( int i = 7 ; i >= 0 ; i-- ) {
		GL_SelectTextureNoClient( i );
		globalImages->BindNull();
	}
	GL_State( 0 );

	RB_EXP_Bloom();
	RB_EXP_GammaDither();

	// these haven't been state saved
	for ( int i = 0 ; i < 8 ; i++ ) {
		backEnd.glState.tmu[i].current2DMap = -1;
	}

	// take it out of texture compare mode so I can testImage it for debugging
	shadowImage[0]->BindFragment();
	qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );


}



/*
==================
R_Exp_Init
==================
*/
void R_Exp_Init( void ) {
	glConfig.allowExpPath = false;

	common->Printf( "---------- R_Exp_Init ----------\n" );

	if ( !glConfig.ARBVertexProgramAvailable || !glConfig.ARBFragmentProgramAvailable ) {
		common->Printf( "Not available.\n" );
		return;
	}
RB_CreateBloomTable();
#if 0
	if ( !R_CheckExtension( "GL_NV_float_buffer" ) ) {
		common->Printf( "Not available.\n" );
		return;
	}
	if ( !R_CheckExtension( "GL_NV_texture_rectangle" ) ) {
		common->Printf( "Not available.\n" );
		return;
	}
#endif

#if 0
	qglCombinerParameterfvNV = (void (APIENTRY *)( GLenum pname, const GLfloat *params ))
		GLimp_ExtensionPointer( "glCombinerParameterfvNV" );
#endif

	common->Printf( "Available.\n" );

	if ( !idStr::Icmp( r_renderer.GetString(), "exp" ) ) {
		R_Exp_Allocate();
	}

	common->Printf( "--------------------------------------------\n" );

	glConfig.allowExpPath = true;
}