/** 
 * @file lldrawpoolbump.cpp
 * @brief LLDrawPoolBump class implementation
 *
 * $LicenseInfo:firstyear=2003&license=viewergpl$
 * 
 * Copyright (c) 2003-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "lldrawpoolbump.h"

#include "llstl.h"
#include "llviewercontrol.h"
#include "lldir.h"
#include "llimagegl.h"
#include "m3math.h"
#include "m4math.h"
#include "v4math.h"
#include "llglheaders.h"
#include "llglimmediate.h"

#include "llagent.h"
#include "llcubemap.h"
#include "lldrawable.h"
#include "llface.h"
#include "llsky.h"
#include "lltextureentry.h"
#include "llviewercamera.h"
#include "llviewerimagelist.h"
#include "pipeline.h"
#include "llspatialpartition.h"
#include "llglslshader.h"

//#include "llimagebmp.h"
//#include "../tools/imdebug/imdebug.h"

// static
LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; 

// static
U32 LLStandardBumpmap::sStandardBumpmapCount = 0;

// static
LLBumpImageList gBumpImageList;

const S32 STD_BUMP_LATEST_FILE_VERSION = 1;

const U32 VERTEX_MASK_SHINY = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR;
const U32 VERTEX_MASK_BUMP = LLVertexBuffer::MAP_VERTEX |LLVertexBuffer::MAP_TEXCOORD | LLVertexBuffer::MAP_TEXCOORD2;

U32 LLDrawPoolBump::sVertexMask = VERTEX_MASK_SHINY;
static LLPointer<LLCubeMap> sCubeMap;

static LLGLSLShader* shader = NULL;
static S32 cube_channel = -1;
static S32 diffuse_channel = -1;

// static 
void LLStandardBumpmap::init()
{
	LLStandardBumpmap::restoreGL();
}

// static 
void LLStandardBumpmap::shutdown()
{
	LLStandardBumpmap::destroyGL();
}

// static 
void LLStandardBumpmap::restoreGL()
{
	llassert( LLStandardBumpmap::sStandardBumpmapCount == 0 );
	gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("None");		// BE_NO_BUMP
	gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Brightness");	// BE_BRIGHTNESS
	gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Darkness");	// BE_DARKNESS

	std::string file_name = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "std_bump.ini" );
	FILE* file = LLFile::fopen( file_name.c_str(), "rt" );	 /*Flawfinder: ignore*/
	if( !file )
	{
		llwarns << "Could not open std_bump <" << file_name << ">" << llendl;
		return;
	}

	S32 file_version = 0;

	S32 fields_read = fscanf( file, "LLStandardBumpmap version %d", &file_version );
	if( fields_read != 1 )
	{
		llwarns << "Bad LLStandardBumpmap header" << llendl;
		return;
	}

	if( file_version > STD_BUMP_LATEST_FILE_VERSION )
	{
		llwarns << "LLStandardBumpmap has newer version (" << file_version << ") than viewer (" << STD_BUMP_LATEST_FILE_VERSION << ")" << llendl;
		return;
	}

	while( !feof(file) && (LLStandardBumpmap::sStandardBumpmapCount < (U32)TEM_BUMPMAP_COUNT) )
	{
		// *NOTE: This buffer size is hard coded into scanf() below.
		char label[2048] = "";	/* Flawfinder: ignore */
		char bump_file[2048] = "";	/* Flawfinder: ignore */
		fields_read = fscanf(	/* Flawfinder: ignore */
			file, "\n%2047s %2047s", label, bump_file);
		if( EOF == fields_read )
		{
			break;
		}
		if( fields_read != 2 )
		{
			llwarns << "Bad LLStandardBumpmap entry" << llendl;
			return;
		}

// 		llinfos << "Loading bumpmap: " << bump_file << " from viewerart" << llendl;
		gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mLabel = label;
		gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage = 
			gImageList.getImageFromFile(bump_file,
										TRUE, 
										FALSE, 
										0, 
										0);
		LLStandardBumpmap::sStandardBumpmapCount++;
	}

	fclose( file );
}

// static
void LLStandardBumpmap::destroyGL()
{
	for( U32 i = 0; i < LLStandardBumpmap::sStandardBumpmapCount; i++ )
	{
		gStandardBumpmapList[i].mLabel.assign("");
		gStandardBumpmapList[i].mImage = NULL;
	}
	sStandardBumpmapCount = 0;
}



////////////////////////////////////////////////////////////////

LLDrawPoolBump::LLDrawPoolBump() 
:  LLRenderPass(LLDrawPool::POOL_BUMP)
{
	mShiny = FALSE;
}


void LLDrawPoolBump::prerender()
{
	mVertexShaderLevel = LLShaderMgr::getVertexShaderLevel(LLShaderMgr::SHADER_OBJECT);
}

// static
S32 LLDrawPoolBump::numBumpPasses()
{
	if (gSavedSettings.getBOOL("RenderObjectBump"))
	{
		if (mVertexShaderLevel > 1)
		{
			if (LLPipeline::sImpostorRender)
			{
				return 2;
			}
			else
			{
				return 3;
			}
		}
		else if (LLPipeline::sImpostorRender)
		{
			return 1;
		}
		else
		{
			return 2;
		}
	}
    else
	{
		return 0;
	}
}

S32 LLDrawPoolBump::getNumPasses()
{
	return numBumpPasses();
}

void LLDrawPoolBump::beginRenderPass(S32 pass)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_BUMP);
	switch( pass )
	{
		case 0:
			beginShiny();
			break;
		case 1:
			if (mVertexShaderLevel > 1)
			{
				beginFullbrightShiny();
			}
			else 
			{
				beginBump();
			}
			break;
		case 2:
			beginBump();
			break;
		default:
			llassert(0);
			break;
	}
}

void LLDrawPoolBump::render(S32 pass)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_BUMP);
	
	if (!gPipeline.hasRenderType(LLDrawPool::POOL_SIMPLE))
	{
		return;
	}
	
	switch( pass )
	{
		case 0:
			renderShiny();
			break;
		case 1:
			if (mVertexShaderLevel > 1)
			{
				renderFullbrightShiny();
			}
			else 
			{
				renderBump(); 
			}
			break;
		case 2:
			renderBump();
			break;
		default:
			llassert(0);
			break;
	}
}

void LLDrawPoolBump::endRenderPass(S32 pass)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_BUMP);
	switch( pass )
	{
		case 0:
			endShiny();
			break;
		case 1:
			if (mVertexShaderLevel > 1)
			{
				endFullbrightShiny();
			}
			else 
			{
				endBump();
			}
			break;
		case 2:
			endBump();
			break;
		default:
			llassert(0);
			break;
	}
}

//static
void LLDrawPoolBump::beginShiny(bool invisible)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY)|| 
		invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_INVISI_SHINY))
	{
		return;
	}

	mShiny = TRUE;
	sVertexMask = VERTEX_MASK_SHINY;
	// Second pass: environment map
	if (!invisible && mVertexShaderLevel > 1)
	{
		sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD;
	}
	
	if (LLPipeline::sUnderWaterRender)
	{
		shader = &gObjectShinyWaterProgram;
	}
	else
	{
		shader = &gObjectShinyProgram;
	}

	LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
	if( cube_map )
	{
		if (!invisible && LLShaderMgr::getVertexShaderLevel(LLShaderMgr::SHADER_OBJECT) > 0 )
		{
			LLMatrix4 mat;
			mat.initRows(LLVector4(gGLModelView+0),
						 LLVector4(gGLModelView+4),
						 LLVector4(gGLModelView+8),
						 LLVector4(gGLModelView+12));
			shader->bind();
			LLVector3 vec = LLVector3(gShinyOrigin) * mat;
			LLVector4 vec4(vec, gShinyOrigin.mV[3]);
			shader->uniform4fv(LLShaderMgr::SHINY_ORIGIN, 1, vec4.mV);			
			if (mVertexShaderLevel > 1)
			{
				cube_map->setMatrix(1);
				// Make sure that texture coord generation happens for tex unit 1, as that's the one we use for 
				// the cube map in the one pass shiny shaders
				cube_channel = shader->enableTexture(LLShaderMgr::ENVIRONMENT_MAP, GL_TEXTURE_CUBE_MAP_ARB);
				cube_map->enableTexture(cube_channel);
				cube_map->enableTextureCoords(1);
				diffuse_channel = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP);
			}
			else
			{
				cube_channel = 0;
				diffuse_channel = -1;
				cube_map->setMatrix(0);
				cube_map->enable(shader->enableTexture(LLShaderMgr::ENVIRONMENT_MAP, GL_TEXTURE_CUBE_MAP_ARB));
			}			
			cube_map->bind();
		}
		else
		{
			cube_channel = 0;
			diffuse_channel = -1;
			cube_map->enable(0);
			cube_map->setMatrix(0);
			cube_map->bind();

			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,	GL_COMBINE_ARB);
			
			//use RGB from texture
			glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB,	GL_REPLACE);
			glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB,	GL_TEXTURE);
			glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB,	GL_SRC_COLOR);

			// use alpha from color
			glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB,		GL_REPLACE);
			glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB,		GL_PRIMARY_COLOR);
			glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB,	GL_SRC_ALPHA);
		}
	}
}

void LLDrawPoolBump::renderShiny(bool invisible)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY)|| 
		invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_INVISI_SHINY))
	{
		return;
	}

	sCubeMap = NULL;

	if( gSky.mVOSkyp->getCubeMap() )
	{
		LLGLEnable blend_enable(GL_BLEND);
		if (!invisible && mVertexShaderLevel > 1)
		{
			LLRenderPass::renderTexture(LLRenderPass::PASS_SHINY, sVertexMask);
		}
		else if (!invisible)
		{
			renderGroups(LLRenderPass::PASS_SHINY, sVertexMask);
		}
		else // invisible
		{
			renderGroups(LLRenderPass::PASS_INVISI_SHINY, sVertexMask);
		}
	}
}

void LLDrawPoolBump::endShiny(bool invisible)
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_SHINY)|| 
		invisible && !gPipeline.hasRenderBatches(LLRenderPass::PASS_INVISI_SHINY))
	{
		return;
	}

	LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
	if( cube_map )
	{
		cube_map->disable();
		cube_map->restoreMatrix();

		if (!invisible && mVertexShaderLevel > 1)
		{
			shader->disableTexture(LLShaderMgr::ENVIRONMENT_MAP, GL_TEXTURE_CUBE_MAP_ARB);
					
			if (LLShaderMgr::getVertexShaderLevel(LLShaderMgr::SHADER_OBJECT) > 0)
			{
				if (diffuse_channel != 0)
				{
					shader->disableTexture(LLShaderMgr::DIFFUSE_MAP);
				}
			}

			shader->unbind();
			glActiveTextureARB(GL_TEXTURE0_ARB);
			glEnable(GL_TEXTURE_2D);
		}

		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	}
	
	LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	
	diffuse_channel = -1;
	cube_channel = 0;
	mShiny = FALSE;
}

void LLDrawPoolBump::beginFullbrightShiny()
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
	{
		return;
	}

	sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD;

	// Second pass: environment map
	
	if (LLPipeline::sUnderWaterRender)
	{
		shader = &gObjectShinyWaterProgram;
	}
	else
	{
		shader = &gObjectFullbrightShinyProgram;
	}

	LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
	if( cube_map )
	{
		LLMatrix4 mat;
		mat.initRows(LLVector4(gGLModelView+0),
					 LLVector4(gGLModelView+4),
					 LLVector4(gGLModelView+8),
					 LLVector4(gGLModelView+12));
		shader->bind();
		LLVector3 vec = LLVector3(gShinyOrigin) * mat;
		LLVector4 vec4(vec, gShinyOrigin.mV[3]);
		shader->uniform4fv(LLShaderMgr::SHINY_ORIGIN, 1, vec4.mV);			

		cube_map->setMatrix(1);
		// Make sure that texture coord generation happens for tex unit 1, as that's the one we use for 
		// the cube map in the one pass shiny shaders
		cube_channel = shader->enableTexture(LLShaderMgr::ENVIRONMENT_MAP, GL_TEXTURE_CUBE_MAP_ARB);
		cube_map->enableTexture(cube_channel);
		cube_map->enableTextureCoords(1);
		diffuse_channel = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP);

		cube_map->bind();
	}
	mShiny = TRUE;
}

void LLDrawPoolBump::renderFullbrightShiny()
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
	{
		return;
	}

	sCubeMap = NULL;

	if( gSky.mVOSkyp->getCubeMap() )
	{
		LLGLEnable blend_enable(GL_BLEND);
		LLRenderPass::renderTexture(LLRenderPass::PASS_FULLBRIGHT_SHINY, sVertexMask);
	}
}

void LLDrawPoolBump::endFullbrightShiny()
{
	LLFastTimer t(LLFastTimer::FTM_RENDER_SHINY);
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY))
	{
		return;
	}

	LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
	if( cube_map )
	{
		cube_map->disable();
		cube_map->restoreMatrix();

		if (diffuse_channel != 0)
		{
			shader->disableTexture(LLShaderMgr::DIFFUSE_MAP);
		}
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glEnable(GL_TEXTURE_2D);

		shader->unbind();

		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	}
	
	LLImageGL::unbindTexture(0, GL_TEXTURE_2D);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	diffuse_channel = -1;
	cube_channel = 0;
	mShiny = FALSE;
}

void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE)
{					
	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];	
	
	for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) 
	{
		LLDrawInfo& params = **k;
		if (LLPipeline::sDynamicReflections)
		{
			if (params.mReflectionMap.notNull())
			{
				params.mReflectionMap->bind();
			}
			else
			{
				if (params.mModelMatrix)
				{
					sCubeMap = gPipeline.findReflectionMap(params.mModelMatrix->getTranslation());	
				}

				if (sCubeMap)
				{
					sCubeMap->bind();
				}
				else if (gSky.mVOSkyp->getCubeMap())
				{
					gSky.mVOSkyp->getCubeMap()->bind();
				}
			}
		}
		
		applyModelMatrix(params);

		params.mVertexBuffer->setBuffer(mask);
		params.mVertexBuffer->drawRange(LLVertexBuffer::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset);
		gPipeline.addTrianglesDrawn(params.mCount/3);
	}
}


// static
BOOL LLDrawPoolBump::bindBumpMap(LLDrawInfo& params)
{
	LLImageGL* bump = NULL;

	U8 bump_code = params.mBump;
	LLViewerImage* tex = params.mTexture;

	switch( bump_code )
	{
	case BE_NO_BUMP:
		bump = NULL;
		break;
	case BE_BRIGHTNESS: 
	case BE_DARKNESS:
		if( tex )
		{
			bump = gBumpImageList.getBrightnessDarknessImage( tex, bump_code );
		}
		break;

	default:
		if( bump_code < LLStandardBumpmap::sStandardBumpmapCount )
		{
			bump = gStandardBumpmapList[bump_code].mImage;
			gBumpImageList.addTextureStats(bump_code, tex->getID(), params.mVSize, 1, 1);
		}
		break;
	}

	if (bump)
	{
		bump->bind(1);
		bump->bind(0);
		return TRUE;
	}
	return FALSE;
}

//static
void LLDrawPoolBump::beginBump()
{	
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
	{
		return;
	}

	sVertexMask = VERTEX_MASK_BUMP;
	LLFastTimer t(LLFastTimer::FTM_RENDER_BUMP);
	// Optional second pass: emboss bump map
	stop_glerror();

	// TEXTURE UNIT 0
	// Output.rgb = texture at texture coord 0
	glActiveTextureARB(GL_TEXTURE0_ARB);

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,	GL_COMBINE_ARB);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB,	GL_REPLACE);

	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB,	GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB,	GL_SRC_ALPHA);

	// Don't care about alpha output
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB,		GL_REPLACE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB,		GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB,	GL_SRC_ALPHA);

	// TEXTURE UNIT 1
	glActiveTextureARB(GL_TEXTURE1_ARB);

	glEnable(GL_TEXTURE_2D); // Texture unit 1

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,	GL_COMBINE_ARB);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB,	GL_ADD_SIGNED_ARB);

	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB,	GL_PREVIOUS_ARB);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB,	GL_SRC_COLOR);

	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB,	GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB,	GL_ONE_MINUS_SRC_ALPHA);

	// Don't care about alpha output
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB,		GL_REPLACE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB,		GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB,	GL_SRC_ALPHA);

	// src	= tex0 + (1 - tex1) - 0.5
	//		= (bump0/2 + 0.5) + (1 - (bump1/2 + 0.5)) - 0.5
	//		= (1 + bump0 - bump1) / 2


	// Blend: src * dst + dst * src
	//		= 2 * src * dst
	//		= 2 * ((1 + bump0 - bump1) / 2) * dst   [0 - 2 * dst]
	//		= (1 + bump0 - bump1) * dst.rgb
	//		= dst.rgb + dst.rgb * (bump0 - bump1)
	gGL.blendFunc(GL_DST_COLOR, GL_SRC_COLOR);
//	gGL.blendFunc(GL_ONE, GL_ZERO);  // temp
	glActiveTextureARB(GL_TEXTURE0_ARB);
	stop_glerror();

	LLViewerImage::unbindTexture(1, GL_TEXTURE_2D);
}

//static
void LLDrawPoolBump::renderBump()
{
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
	{
		return;
	}

	LLFastTimer ftm(LLFastTimer::FTM_RENDER_BUMP);
	LLGLDisable fog(GL_FOG);
	LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_LEQUAL);
	LLGLEnable blend(GL_BLEND);
	glColor4f(1,1,1,1);
	/// Get rid of z-fighting with non-bump pass.
	LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(-1.0f, -1.0f);
	renderBump(LLRenderPass::PASS_BUMP, sVertexMask);
}

//static
void LLDrawPoolBump::endBump()
{
	if (!gPipeline.hasRenderBatches(LLRenderPass::PASS_BUMP))
	{
		return;
	}

	// Disable texture unit 1
	glActiveTextureARB(GL_TEXTURE1_ARB);
	glDisable(GL_TEXTURE_2D); // Texture unit 1
	glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	// Disable texture unit 0
	glActiveTextureARB(GL_TEXTURE0_ARB);
	glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	
	gGL.blendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}

////////////////////////////////////////////////////////////////
// List of one-component bump-maps created from other texures.


//const LLUUID TEST_BUMP_ID("3d33eaf2-459c-6f97-fd76-5fce3fc29447");

void LLBumpImageList::init()
{
	llassert( mBrightnessEntries.size() == 0 );
	llassert( mDarknessEntries.size() == 0 );

	LLStandardBumpmap::init();
}

void LLBumpImageList::shutdown()
{
	mBrightnessEntries.clear();
	mDarknessEntries.clear();
	LLStandardBumpmap::shutdown();
}

void LLBumpImageList::destroyGL()
{
	mBrightnessEntries.clear();
	mDarknessEntries.clear();
	LLStandardBumpmap::destroyGL();
}

void LLBumpImageList::restoreGL()
{
	// Images will be recreated as they are needed.
	LLStandardBumpmap::restoreGL();
}


LLBumpImageList::~LLBumpImageList()
{
	// Shutdown should have already been called.
	llassert( mBrightnessEntries.size() == 0 );
	llassert( mDarknessEntries.size() == 0 );
}


// Note: Does nothing for entries in gStandardBumpmapList that are not actually standard bump images (e.g. none, brightness, and darkness)
void LLBumpImageList::addTextureStats(U8 bump, const LLUUID& base_image_id,
									  F32 pixel_area, F32 texel_area_ratio, F32 cos_center_angle)
{
	bump &= TEM_BUMP_MASK;
	LLViewerImage* bump_image = gStandardBumpmapList[bump].mImage;
	if( bump_image )
	{
		bump_image->addTextureStats(pixel_area, texel_area_ratio, cos_center_angle);
	}
}


void LLBumpImageList::updateImages()
{	
	for (bump_image_map_t::iterator iter = mBrightnessEntries.begin(); iter != mBrightnessEntries.end(); )
	{
		bump_image_map_t::iterator curiter = iter++;
		LLImageGL* image = curiter->second;
		if( image )
		{
			BOOL destroy = TRUE;
			if( image->getHasGLTexture())
			{
				if( image->getBoundRecently() )
				{
					destroy = FALSE;
				}
				else
				{
					image->destroyGLTexture();
				}
			}

			if( destroy )
			{
				//llinfos << "*** Destroying bright " << (void*)image << llendl;
				mBrightnessEntries.erase(curiter);   // deletes the image thanks to reference counting
			}
		}
	}

	for (bump_image_map_t::iterator iter = mDarknessEntries.begin(); iter != mDarknessEntries.end(); )
	{
		bump_image_map_t::iterator curiter = iter++;
		LLImageGL* image = curiter->second;
		if( image )
		{
			BOOL destroy = TRUE;
			if( image->getHasGLTexture())
			{
				if( image->getBoundRecently() )
				{
					destroy = FALSE;
				}
				else
				{
					image->destroyGLTexture();
				}
			}

			if( destroy )
			{
				//llinfos << "*** Destroying dark " << (void*)image << llendl;;
				mDarknessEntries.erase(curiter);  // deletes the image thanks to reference counting
			}
		}
	}

}


// Note: the caller SHOULD NOT keep the pointer that this function returns.  It may be updated as more data arrives.
LLImageGL* LLBumpImageList::getBrightnessDarknessImage(LLViewerImage* src_image, U8 bump_code )
{
	llassert( (bump_code == BE_BRIGHTNESS) || (bump_code == BE_DARKNESS) );

	LLImageGL* bump = NULL;
	const F32 BRIGHTNESS_DARKNESS_PIXEL_AREA_THRESHOLD = 1000;
	if( src_image->mMaxVirtualSize > BRIGHTNESS_DARKNESS_PIXEL_AREA_THRESHOLD )
	{
		bump_image_map_t* entries_list = NULL;
		void (*callback_func)( BOOL success, LLViewerImage *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata ) = NULL;

		switch( bump_code )
		{
		case BE_BRIGHTNESS:
			entries_list = &mBrightnessEntries;
			callback_func = LLBumpImageList::onSourceBrightnessLoaded;
			break;
		case BE_DARKNESS:
			entries_list = &mDarknessEntries;
			callback_func = LLBumpImageList::onSourceDarknessLoaded;
			break;
		default:
			llassert(0);
			return NULL;
		}

		bump_image_map_t::iterator iter = entries_list->find(src_image->getID());
		if (iter != entries_list->end())
		{
			bump = iter->second;
		}
		else
		{
			LLPointer<LLImageRaw> raw = new LLImageRaw(1,1,1);
			raw->clear(0x77, 0x77, 0x77, 0xFF);
			bump = new LLImageGL( raw, TRUE);
			bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
			(*entries_list)[src_image->getID()] = bump;

			// Note: this may create an LLImageGL immediately
			src_image->setLoadedCallback( callback_func, 0, TRUE, new LLUUID(src_image->getID()) );
			bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image

//			bump_total++;
//			llinfos << "*** Creating " << (void*)bump << " " << bump_total << llendl;
		}
	}

	return bump;
}


// static
void LLBumpImageList::onSourceBrightnessLoaded( BOOL success, LLViewerImage *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
{
	LLUUID* source_asset_id = (LLUUID*)userdata;
	LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_BRIGHTNESS );
	if( final )
	{
		delete source_asset_id;
	}
}

// static
void LLBumpImageList::onSourceDarknessLoaded( BOOL success, LLViewerImage *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata )
{
	LLUUID* source_asset_id = (LLUUID*)userdata;
	LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_DARKNESS );
	if( final )
	{
		delete source_asset_id;
	}
}


// static
void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerImage *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump_code )
{
	if( success )
	{
		bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries );
		bump_image_map_t::iterator iter = entries_list.find(source_asset_id);
		if (iter != entries_list.end())
		{
			LLPointer<LLImageRaw> dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1);
			U8* dst_data = dst_image->getData();
			S32 dst_data_size = dst_image->getDataSize();

			U8* src_data = src->getData();
			S32 src_data_size = src->getDataSize();

			S32 src_components = src->getComponents();

			// Convert to luminance and then scale and bias that to get ready for
			// embossed bump mapping.  (0-255 maps to 127-255)

			// Convert to fixed point so we don't have to worry about precision/clamping.
			const S32 FIXED_PT = 8;
			const S32 R_WEIGHT = S32(0.2995f * (1<<FIXED_PT));
			const S32 G_WEIGHT = S32(0.5875f * (1<<FIXED_PT));
			const S32 B_WEIGHT = S32(0.1145f * (1<<FIXED_PT));

			S32 minimum = 255;
			S32 maximum = 0;

			switch( src_components )
			{
			case 1:
			case 2:
				if( src_data_size == dst_data_size * src_components )
				{
					for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
					{
						dst_data[i] = src_data[j];
						if( dst_data[i] < minimum )
						{
							minimum = dst_data[i];
						}
						if( dst_data[i] > maximum )
						{
							maximum = dst_data[i];
						}
					}
				}
				else
				{
					llassert(0);
					dst_image->clear();
				}
				break;
			case 3:
			case 4:
				if( src_data_size == dst_data_size * src_components )
				{
					for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
					{
						// RGB to luminance
						dst_data[i] = (R_WEIGHT * src_data[j] + G_WEIGHT * src_data[j+1] + B_WEIGHT * src_data[j+2]) >> FIXED_PT;
						//llassert( dst_data[i] <= 255 );true because it's 8bit
						if( dst_data[i] < minimum )
						{
							minimum = dst_data[i];
						}
						if( dst_data[i] > maximum )
						{
							maximum = dst_data[i];
						}
					}
				}
				else
				{
					llassert(0);
					dst_image->clear();
				}
				break;
			default:
				llassert(0);
				dst_image->clear();
				break;
			}

			if( maximum > minimum )
			{
				U8 bias_and_scale_lut[256];
				F32 twice_one_over_range = 2.f / (maximum - minimum);
				S32 i;

				const F32 ARTIFICIAL_SCALE = 2.f;  // Advantage: exagerates the effect in midrange.  Disadvantage: clamps at the extremes.
				if( BE_DARKNESS == bump_code )
				{
					for( i = minimum; i <= maximum; i++ )
					{
						F32 minus_one_to_one = F32(maximum - i) * twice_one_over_range - 1.f;
						bias_and_scale_lut[i] = llclampb(llround(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
					}
				}
				else
				{
					// BE_LIGHTNESS
					for( i = minimum; i <= maximum; i++ )
					{
						F32 minus_one_to_one = F32(i - minimum) * twice_one_over_range - 1.f;
						bias_and_scale_lut[i] = llclampb(llround(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
					}
				}

				for( i = 0; i < dst_data_size; i++ )
				{
					dst_data[i] = bias_and_scale_lut[dst_data[i]];
				}
			}

			LLImageGL* bump = new LLImageGL( TRUE);
			bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
			bump->createGLTexture(0, dst_image);
			iter->second = bump; // derefs (and deletes) old image
		}
		else
		{
			// entry should have been added in LLBumpImageList::getImage().

			// Not a legit assertion - the bump texture could have been flushed by the bump image manager
			//llassert(0);
		}
	}
}

void LLDrawPoolBump::renderBump(U32 type, U32 mask)
{	
	LLCullResult::drawinfo_list_t::iterator begin = gPipeline.beginRenderMap(type);
	LLCullResult::drawinfo_list_t::iterator end = gPipeline.endRenderMap(type);

	for (LLCullResult::drawinfo_list_t::iterator i = begin; i != end; ++i)	
	{
		LLDrawInfo& params = **i;

		if (LLDrawPoolBump::bindBumpMap(params))
		{
			pushBatch(params, mask, FALSE);
		}
	}
}

void LLDrawPoolBump::renderGroupBump(LLSpatialGroup* group, U32 type, U32 mask)
{					
	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];	
	
	for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) 
	{
		LLDrawInfo& params = **k;
		
		if (LLDrawPoolBump::bindBumpMap(params))
		{
			pushBatch(params, mask, FALSE);
		}
	}
}

void LLDrawPoolBump::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture)
{
	applyModelMatrix(params);

	if (params.mTextureMatrix)
	{
		if (mShiny)
		{
			glActiveTextureARB(GL_TEXTURE0_ARB);
			glMatrixMode(GL_TEXTURE);
		}
		else
		{
			glActiveTextureARB(GL_TEXTURE1_ARB);
			glMatrixMode(GL_TEXTURE);
			glLoadMatrixf((GLfloat*) params.mTextureMatrix->mMatrix);
			gPipeline.mTextureMatrixOps++;
			glActiveTextureARB(GL_TEXTURE0_ARB);
		}

		glLoadMatrixf((GLfloat*) params.mTextureMatrix->mMatrix);
		gPipeline.mTextureMatrixOps++;
	}

	if (mShiny && mVertexShaderLevel > 1 && texture)
	{
		if (params.mTexture.notNull())
		{
			params.mTexture->bind(diffuse_channel);
			params.mTexture->addTextureStats(params.mVSize);
		}
		else
		{
			LLImageGL::unbindTexture(0);
		}

		if (LLPipeline::sDynamicReflections)
		{
			LLCubeMap* cube_map = params.mReflectionMap;

			if (!cube_map && params.mModelMatrix)
			{
				cube_map = gPipeline.findReflectionMap(params.mModelMatrix->getTranslation());
			}

			if (cube_map)
			{
				cube_map->enableTexture(cube_channel);
				cube_map->bind();
			}	
		}
	}
	
	params.mVertexBuffer->setBuffer(mask);
	params.mVertexBuffer->drawRange(LLVertexBuffer::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset);
	gPipeline.addTrianglesDrawn(params.mCount/3);
	if (params.mTextureMatrix)
	{
		if (mShiny)
		{
			glActiveTextureARB(GL_TEXTURE0_ARB);
		}
		else
		{
			glActiveTextureARB(GL_TEXTURE1_ARB);
			glLoadIdentity();
			glActiveTextureARB(GL_TEXTURE0_ARB);
		}
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
	}
}

void LLDrawPoolInvisible::render(S32 pass)
{ //render invisiprims
	LLFastTimer t(LLFastTimer::FTM_RENDER_INVISIBLE);
  
	U32 invisi_mask = LLVertexBuffer::MAP_VERTEX;
	glStencilMask(0);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	pushBatches(LLRenderPass::PASS_INVISIBLE, invisi_mask, FALSE);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
	glStencilMask(0xFFFFFFFF);

	if (gPipeline.hasRenderBatches(LLRenderPass::PASS_INVISI_SHINY))
	{
		beginShiny(true);
		renderShiny(true);
		endShiny(true);
	}
}