diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index 049dd4346b2feb7dd297c1a72163a41ab88fe77f..053251099650a45dd9dad364afda5d3954e15810 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -203,7 +203,9 @@ void LLTexUnit::disable(void)
 		if (mCurrTexType != LLTexUnit::TT_MULTISAMPLE_TEXTURE &&
 			mIndex < gGLManager.mNumTextureUnits)
 		{
+			stop_glerror();
 			glDisable(sGLTextureType[mCurrTexType]);
+			stop_glerror();
 		}
 		
 		mCurrTexType = TT_NONE;
@@ -403,6 +405,7 @@ void LLTexUnit::unbind(eTextureType type)
 		activate();
 		mCurrTexture = 0;
 		glBindTexture(sGLTextureType[type], 0);
+		stop_glerror();
 	}
 }
 
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index 8c9171ccf45cedbb24fe1cb3890948f17093f190..f715a8e9ba76e78bc8863bdfc62eddedf289f33e 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -1497,7 +1497,14 @@ void LLVertexBuffer::setupVertexBuffer(U32 data_mask) const
 	}
 	if (data_mask & MAP_VERTEX)
 	{
-		glVertexPointer(3,GL_FLOAT, LLVertexBuffer::sTypeSize[TYPE_VERTEX], (void*)(base + 0));
+		if (data_mask & MAP_TEXTURE_INDEX)
+		{
+			glVertexPointer(4,GL_FLOAT, LLVertexBuffer::sTypeSize[TYPE_VERTEX], (void*)(base + 0));
+		}
+		else
+		{
+			glVertexPointer(3,GL_FLOAT, LLVertexBuffer::sTypeSize[TYPE_VERTEX], (void*)(base + 0));
+		}
 	}
 
 	llglassertok();
diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h
index a9f22193f87a4601aa25b94da85ad4fdb7ded85b..0c4b2415373fa35af62ea05d201a29872eeec643 100644
--- a/indra/llrender/llvertexbuffer.h
+++ b/indra/llrender/llvertexbuffer.h
@@ -130,6 +130,9 @@ class LLVertexBuffer : public LLRefCount
 		TYPE_CLOTHWEIGHT,
 		TYPE_MAX,
 		TYPE_INDEX,
+		
+		//no actual additional data, but indicates position.w is texture index
+		TYPE_TEXTURE_INDEX,
 	};
 	enum {
 		MAP_VERTEX = (1<<TYPE_VERTEX),
@@ -144,6 +147,7 @@ class LLVertexBuffer : public LLRefCount
 		MAP_WEIGHT = (1<<TYPE_WEIGHT),
 		MAP_WEIGHT4 = (1<<TYPE_WEIGHT4),
 		MAP_CLOTHWEIGHT = (1<<TYPE_CLOTHWEIGHT),
+		MAP_TEXTURE_INDEX = (1<<TYPE_TEXTURE_INDEX),
 	};
 	
 protected:
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 2c6d75e3c9f11de09037b4b9957e02ddda440dcd..22d8ce49c19b42f9a0039fe5ec8fa6c75d7a23a3 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -7489,7 +7489,7 @@
       <key>Type</key>
       <string>Boolean</string>
       <key>Value</key>
-      <integer>0</integer>
+      <integer>1</integer>
     </map>
   <key>RenderDebugNormalScale</key>
   <map>
diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl
index 35cfb80c93882795211bd29c2834427513ac0a2b..f71d9f0b49e3dfc2918acbfb4154184bb19a4098 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl
@@ -20,3 +20,4 @@ void main()
 	vec3 nvn = normalize(vary_normal);
 	gl_FragData[2] = vec4(nvn.xy * 0.5 + 0.5, nvn.z, 0.0);
 }
+
diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseV.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseV.glsl
index 03d3322cb68855adfc5a7c9fe0de09b73ae96a19..b8de629fc86f66b78cdba0402c7c340378a60cc0 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/diffuseV.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseV.glsl
@@ -8,13 +8,15 @@
 #version 120
 
 varying vec3 vary_normal;
+varying float vary_texture_index;
 
 void main()
 {
 	//transform vertex
-	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
+	gl_Position = gl_ModelViewProjectionMatrix * vec4(gl_Vertex.xyz, 1.0); 
 	gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
 	
+	vary_texture_index = gl_Vertex.w;
 	vary_normal = normalize(gl_NormalMatrix * gl_Normal);
 
 	gl_FrontColor = gl_Color;
diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl
index 34298773974b2d35ea7396dceaebf3b7e1d363b9..30231039b0422784db1da230f2a30b8219184992 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl
@@ -9,56 +9,48 @@
 
 #extension GL_ARB_texture_rectangle : enable
 
-uniform sampler2D diffuseMap;
-uniform sampler2DRect depthMap;
-uniform sampler2D noiseMap;
-
-uniform vec4 shadow_clip;
-uniform vec2 screen_res;
+varying float vary_texture_index;
+
+uniform sampler2D tex0;
+uniform sampler2D tex1;
+uniform sampler2D tex2;
+uniform sampler2D tex3;
+uniform sampler2D tex4;
+uniform sampler2D tex5;
+uniform sampler2D tex6;
+uniform sampler2D tex7;
+
+vec4 textureLookup(vec2 texcoord)
+{
+	switch (int(vary_texture_index+0.25))
+	{
+		case 0: return texture2D(tex0, texcoord);
+		case 1: return texture2D(tex1, texcoord);
+		case 2: return texture2D(tex2, texcoord);
+		case 3: return texture2D(tex3, texcoord);
+		case 4: return texture2D(tex4, texcoord);
+		case 5: return texture2D(tex5, texcoord);
+		case 6: return texture2D(tex6, texcoord);
+		case 7: return texture2D(tex7, texcoord);
+	}
+
+	return vec4(0,0,0,0);
+}
 
 vec3 fullbrightAtmosTransport(vec3 light);
 vec3 fullbrightScaleSoftClip(vec3 light);
 
-varying vec3 vary_ambient;
-varying vec3 vary_directional;
-varying vec4 vary_position;
-varying vec3 vary_normal;
-varying vec3 vary_fragcoord;
-
-uniform mat4 inv_proj;
-
-vec4 getPosition(vec2 pos_screen)
-{
-	float depth = texture2DRect(depthMap, pos_screen.xy).a;
-	vec2 sc = pos_screen.xy*2.0;
-	sc /= screen_res;
-	sc -= vec2(1.0,1.0);
-	vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0);
-	vec4 pos = inv_proj * ndc;
-	pos /= pos.w;
-	pos.w = 1.0;
-	return pos;
-}
 
 void main() 
 {
-	vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5;
-	frag *= screen_res;
-	
-	vec3 samp_pos = getPosition(frag).xyz; 
-	
 	float shadow = 1.0;
-	vec4 pos = vary_position;
 
-	vec4 color = texture2D(diffuseMap, gl_TexCoord[0].xy)*gl_Color;
+	vec4 color = textureLookup(gl_TexCoord[0].xy)*gl_Color;
 	
 	color.rgb = fullbrightAtmosTransport(color.rgb);
 
 	color.rgb = fullbrightScaleSoftClip(color.rgb);
 
-	//gl_FragColor = gl_Color;
 	gl_FragColor = color;
-	//gl_FragColor = vec4(1,0,1,1);
-	
 }
 
diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl
index 6c38d220e26962bf7ad251ca51fcc3f7e456c0d0..6890360c566abc8be3dc80aeeab67641a714a700 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl
@@ -14,30 +14,23 @@ vec3 atmosAffectDirectionalLight(float lightIntensity);
 vec3 scaleDownLight(vec3 light);
 vec3 scaleUpLight(vec3 light);
 
-varying vec3 vary_ambient;
-varying vec3 vary_directional;
-varying vec3 vary_normal;
-varying vec3 vary_fragcoord;
-uniform float near_clip;
-varying vec4 vary_position;
+varying float vary_texture_index;
 
 void main()
 {
 	//transform vertex
-	gl_Position = ftransform(); 
+	vec4 vert = vec4(gl_Vertex.xyz, 1.0);
+	vary_texture_index = gl_Vertex.w;
+
+	gl_Position = gl_ModelViewProjectionMatrix*vert; 
 	
 	gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
 	
-	vec4 pos = (gl_ModelViewMatrix * gl_Vertex);
-	vary_position = pos;
-		
+	vec4 pos = (gl_ModelViewMatrix * vert);
+				
 	calcAtmospherics(pos.xyz);
 	
 	gl_FrontColor = gl_Color;
 
 	gl_FogFragCoord = pos.z;
-	
-	pos = gl_ModelViewProjectionMatrix * gl_Vertex;
-	vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip);
-	
 }
diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 25e4bc847c253dcfd41423ef3575751c21c073c5..964569349387e9bd3f314f90f542a4ee7b9efeba 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -430,14 +430,14 @@ void LLRenderPass::renderTexture(U32 type, U32 mask)
 	pushBatches(type, mask, TRUE);
 }
 
-void LLRenderPass::pushBatches(U32 type, U32 mask, BOOL texture)
+void LLRenderPass::pushBatches(U32 type, U32 mask, BOOL texture, BOOL batch_textures)
 {
 	for (LLCullResult::drawinfo_list_t::iterator i = gPipeline.beginRenderMap(type); i != gPipeline.endRenderMap(type); ++i)	
 	{
 		LLDrawInfo* pparams = *i;
 		if (pparams) 
 		{
-			pushBatch(*pparams, mask, texture);
+			pushBatch(*pparams, mask, texture, batch_textures);
 		}
 	}
 }
@@ -456,26 +456,42 @@ void LLRenderPass::applyModelMatrix(LLDrawInfo& params)
 	}
 }
 
-void LLRenderPass::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture)
+void LLRenderPass::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures)
 {
 	applyModelMatrix(params);
 
+	bool tex_setup = false;
+
 	if (texture)
 	{
-		if (params.mTexture.notNull())
+		if (batch_textures && params.mTextureList.size() > 1)
 		{
-			params.mTexture->addTextureStats(params.mVSize);
-			gGL.getTexUnit(0)->bind(params.mTexture, TRUE) ;
-			if (params.mTextureMatrix)
+			for (U32 i = 0; i < params.mTextureList.size(); ++i)
 			{
-				glMatrixMode(GL_TEXTURE);
-				glLoadMatrixf((GLfloat*) params.mTextureMatrix->mMatrix);
-				gPipeline.mTextureMatrixOps++;
+				if (params.mTextureList[i].notNull())
+				{
+					gGL.getTexUnit(i)->bind(params.mTextureList[i], TRUE);
+				}
 			}
 		}
 		else
-		{
-			gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+		{ //not batching textures or batch has only 1 texture -- might need a texture matrix
+			if (params.mTexture.notNull())
+			{
+				params.mTexture->addTextureStats(params.mVSize);
+				gGL.getTexUnit(0)->bind(params.mTexture, TRUE) ;
+				if (params.mTextureMatrix)
+				{
+					tex_setup = true;
+					glMatrixMode(GL_TEXTURE);
+					glLoadMatrixf((GLfloat*) params.mTextureMatrix->mMatrix);
+					gPipeline.mTextureMatrixOps++;
+				}
+			}
+			else
+			{
+				gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+			}
 		}
 	}
 	
@@ -490,7 +506,7 @@ void LLRenderPass::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture)
 		gPipeline.addTrianglesDrawn(params.mCount, params.mDrawMode);
 	}
 
-	if (params.mTextureMatrix && texture && params.mTexture.notNull())
+	if (tex_setup)
 	{
 		glLoadIdentity();
 		glMatrixMode(GL_MODELVIEW);
diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h
index d3fd9ead0d4ef42a88f3a0d0230ffc03d8586b2b..c7acbb42c62da2c8aa5a471e7c8f5a5aaddfd536 100644
--- a/indra/newview/lldrawpool.h
+++ b/indra/newview/lldrawpool.h
@@ -146,8 +146,8 @@ class LLRenderPass : public LLDrawPool
 	void resetDrawOrders() { }
 
 	static void applyModelMatrix(LLDrawInfo& params);
-	virtual void pushBatches(U32 type, U32 mask, BOOL texture = TRUE);
-	virtual void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture);
+	virtual void pushBatches(U32 type, U32 mask, BOOL texture = TRUE, BOOL batch_textures = FALSE);
+	virtual void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures = FALSE);
 	virtual void renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE);
 	virtual void renderGroups(U32 type, U32 mask, BOOL texture = TRUE);
 	virtual void renderTexture(U32 type, U32 mask);
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 8b5a2ce78182d6e4147c73cb09430aaae7453f88..3b3d48ab3676911e5494f67c8b3ca32008d76aca 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -124,7 +124,7 @@ void LLDrawPoolAlpha::beginPostDeferredPass(S32 pass)
 	if (pass == 0)
 	{
 		simple_shader = &gDeferredAlphaProgram;
-		fullbright_shader = &gDeferredFullbrightProgram;
+		fullbright_shader = &gObjectFullbrightProgram;
 	}
 	else
 	{
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 5f89d113917641a515d72e85327aaaffffa2b44e..353107389640f3f8e0955365bb56cd2a6cbc79e5 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -1292,7 +1292,7 @@ void LLDrawPoolBump::renderBump(U32 type, U32 mask)
 	}
 }
 
-void LLDrawPoolBump::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture)
+void LLDrawPoolBump::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures)
 {
 	applyModelMatrix(params);
 
diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h
index f4702bf61d4ed3ed8d8a2c6545cc36f0fcdaf64a..476b1d41b7d40d85ac08d6fb353cabe18d4773b3 100644
--- a/indra/newview/lldrawpoolbump.h
+++ b/indra/newview/lldrawpoolbump.h
@@ -55,7 +55,7 @@ protected :
 	virtual void endRenderPass( S32 pass );
 	virtual S32	 getNumPasses();
 	/*virtual*/ void prerender();
-	/*virtual*/ void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture);
+	/*virtual*/ void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures = FALSE);
 
 	void renderBump(U32 type, U32 mask);
 	void renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture);
diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp
index 2e83167851d7abd61941c00da7889d5a386dc7c7..8ff60f73cc5286820bd300dbbe4f465697f5d033 100644
--- a/indra/newview/lldrawpoolsimple.cpp
+++ b/indra/newview/lldrawpoolsimple.cpp
@@ -44,6 +44,43 @@ static LLGLSLShader* fullbright_shader = NULL;
 static LLFastTimer::DeclareTimer FTM_RENDER_SIMPLE_DEFERRED("Deferred Simple");
 static LLFastTimer::DeclareTimer FTM_RENDER_GRASS_DEFERRED("Deferred Grass");
 
+void LLDrawPoolGlow::beginPostDeferredPass(S32 pass)
+{
+	gDeferredFullbrightProgram.bind();
+}
+
+void LLDrawPoolGlow::renderPostDeferred(S32 pass)
+{
+	LLFastTimer t(FTM_RENDER_GLOW);
+	LLGLEnable blend(GL_BLEND);
+	LLGLDisable test(GL_ALPHA_TEST);
+	gGL.flush();
+	/// Get rid of z-fighting with non-glow pass.
+	LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset(-1.0f, -1.0f);
+	gGL.setSceneBlendType(LLRender::BT_ADD);
+	
+	LLGLDepthTest depth(GL_TRUE, GL_FALSE);
+	gGL.setColorMask(false, true);
+	pushBatches(LLRenderPass::PASS_GLOW, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE);
+	
+	gGL.setColorMask(true, false);
+	gGL.setSceneBlendType(LLRender::BT_ALPHA);	
+}
+
+void LLDrawPoolGlow::endPostDeferredPass(S32 pass)
+{
+	gDeferredFullbrightProgram.unbind();
+	for (U32 i = 0; i < 8; i++)
+	{
+		if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE)
+		{
+			gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType());
+			gGL.getTexUnit(i)->disable();
+		}
+	}
+}
+
 void LLDrawPoolGlow::render(S32 pass)
 {
 	LLFastTimer t(FTM_RENDER_GLOW);
@@ -79,10 +116,10 @@ void LLDrawPoolGlow::render(S32 pass)
 	}
 }
 
-void LLDrawPoolGlow::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture)
+void LLDrawPoolGlow::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures)
 {
 	glColor4ubv(params.mGlowColor.mV);
-	LLRenderPass::pushBatch(params, mask, texture);
+	LLRenderPass::pushBatch(params, mask, texture, batch_textures);
 }
 
 
@@ -128,8 +165,8 @@ void LLDrawPoolSimple::endRenderPass(S32 pass)
 	LLFastTimer t(FTM_RENDER_SIMPLE);
 	LLRenderPass::endRenderPass(pass);
 
-	if (mVertexShaderLevel > 0){
-
+	if (mVertexShaderLevel > 0)
+	{
 		simple_shader->unbind();
 	}
 }
@@ -168,6 +205,15 @@ void LLDrawPoolSimple::endDeferredPass(S32 pass)
 	LLRenderPass::endRenderPass(pass);
 
 	gDeferredDiffuseProgram.unbind();
+
+	for (U32 i = 0; i < 8; i++)
+	{
+		if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE)
+		{
+			gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType());
+			gGL.getTexUnit(i)->disable();
+		}
+	}
 }
 
 void LLDrawPoolSimple::renderDeferred(S32 pass)
@@ -177,7 +223,7 @@ void LLDrawPoolSimple::renderDeferred(S32 pass)
 
 	{ //render simple
 		LLFastTimer t(FTM_RENDER_SIMPLE_DEFERRED);
-		renderTexture(LLRenderPass::PASS_SIMPLE, getVertexDataMask());
+		pushBatches(LLRenderPass::PASS_SIMPLE, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE);
 	}
 }
 
@@ -285,6 +331,33 @@ void LLDrawPoolFullbright::prerender()
 	mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT);
 }
 
+void LLDrawPoolFullbright::beginPostDeferredPass(S32 pass)
+{
+	gDeferredFullbrightProgram.bind();
+}
+
+void LLDrawPoolFullbright::renderPostDeferred(S32 pass)
+{
+	LLFastTimer t(FTM_RENDER_FULLBRIGHT);
+	
+	gGL.setSceneBlendType(LLRender::BT_ALPHA);
+	U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX;
+	pushBatches(LLRenderPass::PASS_FULLBRIGHT, fullbright_mask, TRUE, TRUE);
+}
+
+void LLDrawPoolFullbright::endPostDeferredPass(S32 pass)
+{
+	gDeferredFullbrightProgram.unbind();
+	for (U32 i = 0; i < 8; i++)
+	{
+		if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE)
+		{
+			gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType());
+			gGL.getTexUnit(i)->disable();
+		}
+	}
+}
+
 void LLDrawPoolFullbright::beginRenderPass(S32 pass)
 {
 	LLFastTimer t(FTM_RENDER_FULLBRIGHT);
diff --git a/indra/newview/lldrawpoolsimple.h b/indra/newview/lldrawpoolsimple.h
index 5f3bbebbdad5e6df941150e3f387c61ddfc72880..3811b3d398f872193cb6861e63c053f6f926e263 100644
--- a/indra/newview/lldrawpoolsimple.h
+++ b/indra/newview/lldrawpoolsimple.h
@@ -98,9 +98,9 @@ class LLDrawPoolFullbright : public LLRenderPass
 	LLDrawPoolFullbright();
 	
 	/*virtual*/ S32 getNumPostDeferredPasses() { return 1; }
-	/*virtual*/ void beginPostDeferredPass(S32 pass) { beginRenderPass(pass); }
-	/*virtual*/ void endPostDeferredPass(S32 pass) { endRenderPass(pass); }
-	/*virtual*/ void renderPostDeferred(S32 pass) { render(pass); }
+	/*virtual*/ void beginPostDeferredPass(S32 pass);
+	/*virtual*/ void endPostDeferredPass(S32 pass);
+	/*virtual*/ void renderPostDeferred(S32 pass);
 
 	/*virtual*/ void beginRenderPass(S32 pass);
 	/*virtual*/ void endRenderPass(S32 pass);
@@ -126,12 +126,12 @@ class LLDrawPoolGlow : public LLRenderPass
 	virtual void prerender() { }
 
 	/*virtual*/ S32 getNumPostDeferredPasses() { return 1; }
-	/*virtual*/ void beginPostDeferredPass(S32 pass) { beginRenderPass(pass); }
-	/*virtual*/ void endPostDeferredPass(S32 pass) { endRenderPass(pass); }
-	/*virtual*/ void renderPostDeferred(S32 pass) { render(pass); }
+	/*virtual*/ void beginPostDeferredPass(S32 pass); 
+	/*virtual*/ void endPostDeferredPass(S32 pass);
+	/*virtual*/ void renderPostDeferred(S32 pass);
 
 	void render(S32 pass = 0);
-	void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture = TRUE);
+	void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture = TRUE, BOOL batch_textures = FALSE);
 
 };
 
diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp
index 9f9e50ad0a5c48db4d72a3c266699adbd38ed2b0..e30522d380f601ac060c7883ee858453088e3c95 100644
--- a/indra/newview/llface.cpp
+++ b/indra/newview/llface.cpp
@@ -165,6 +165,7 @@ void LLFace::init(LLDrawable* drawablep, LLViewerObject* objp)
 	mIndexInTex = 0;
 	mTexture		= NULL;
 	mTEOffset		= -1;
+	mTextureIndex = 255;
 
 	setDrawable(drawablep);
 	mVObjp = objp;
@@ -386,6 +387,26 @@ void LLFace::setGeomIndex(U16 idx)
 	}
 }
 
+void LLFace::setTextureIndex(U8 index)
+{
+	if (index != mTextureIndex)
+	{
+		mTextureIndex = index;
+
+		if (mTextureIndex != 255)
+		{
+			mDrawablep->setState(LLDrawable::REBUILD_POSITION);
+		}
+		else
+		{
+			if (mDrawInfo && !mDrawInfo->mTextureList.empty())
+			{
+				llerrs << "Face with no texture index references indexed texture draw info." << llendl;
+			}
+		}
+	}
+}
+
 void LLFace::setIndicesIndex(S32 idx) 
 { 
 	if (mIndicesIndex != idx)
@@ -1573,6 +1594,20 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume,
 			mat_vert.affineTransform(*src++, *dst++);
 		}
 		while(dst < end);
+
+		F32 index = (F32) (mTextureIndex < 255 ? mTextureIndex : 0);
+		F32 *index_dst = (F32*) vertices;
+		F32 *index_end = (F32*) end;
+
+		index_dst += 3;
+		index_end += 3;
+		do
+		{
+			*index_dst = index;
+			index_dst += 4;
+		}
+		while (index_dst < index_end);
+		
 	}
 		
 	if (rebuild_normal)
diff --git a/indra/newview/llface.h b/indra/newview/llface.h
index b2170c4cf3fd271c7cfd63ab7006d752892c0c3b..b5eaeecd60ea18c533f441e7c9b04c51386ef85f 100644
--- a/indra/newview/llface.h
+++ b/indra/newview/llface.h
@@ -94,6 +94,8 @@ class LLFace
 	U16				getGeomCount()		const	{ return mGeomCount; }		// vertex count for this face
 	U16				getGeomIndex()		const	{ return mGeomIndex; }		// index into draw pool
 	U16				getGeomStart()		const	{ return mGeomIndex; }		// index into draw pool
+	void			setTextureIndex(U8 index);
+	U8				getTextureIndex() const		{ return mTextureIndex; }
 	void			setTexture(LLViewerTexture* tex) ;
 	void            switchTexture(LLViewerTexture* new_texture);
 	void            dirtyTexture();
@@ -262,6 +264,7 @@ class LLFace
 	
 	U16			mGeomCount;			// vertex count for this face
 	U16			mGeomIndex;			// index into draw pool
+	U8			mTextureIndex;		// index of texture channel to use for pseudo-atlasing
 	U32			mIndicesCount;
 	U32			mIndicesIndex;		// index into draw pool for indices (yeah, I know!)
 	S32         mIndexInTex ;
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index 8143d6a41f4aa7e89848c2f4970475c34d3ac25a..7f91f9a95271f3d027ebeca3f3700870d2b8c95b 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -3439,6 +3439,8 @@ void renderTextureAnim(LLDrawInfo* params)
 
 void renderBatchSize(LLDrawInfo* params)
 {
+	LLGLEnable offset(GL_POLYGON_OFFSET_FILL);
+	glPolygonOffset(-1.f, 1.f);
 	glColor3ubv((GLubyte*) &(params->mDebugColor));
 	pushVerts(params, LLVertexBuffer::MAP_VERTEX);
 }
@@ -3876,6 +3878,28 @@ class LLOctreeRenderNonOccluded : public LLOctreeTraveler<LLDrawable>
 				renderAgentTarget(avatar);
 			}
 			
+			if (gDebugGL)
+			{
+				for (U32 i = 0; i < drawable->getNumFaces(); ++i)
+				{
+					LLFace* facep = drawable->getFace(i);
+					U8 index = facep->getTextureIndex();
+					if (facep->mDrawInfo)
+					{
+						if (index < 255)
+						{
+							if (facep->mDrawInfo->mTextureList.size() <= index)
+							{
+								llerrs << "Face texture index out of bounds." << llendl;
+							}
+							else if (facep->mDrawInfo->mTextureList[index] != facep->getTexture())
+							{
+								llerrs << "Face texture index incorrect." << llendl;
+							}
+						}
+					}
+				}
+			}
 		}
 		
 		for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i)
diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h
index 0d9cad914a55755344cc062719da89ec364a05d7..ae5d4fa463f72dbc461fb72155f0e2f29f13fa75 100644
--- a/indra/newview/llspatialpartition.h
+++ b/indra/newview/llspatialpartition.h
@@ -91,6 +91,8 @@ class LLDrawInfo : public LLRefCount
 	
 	LLPointer<LLVertexBuffer> mVertexBuffer;
 	LLPointer<LLViewerTexture>     mTexture;
+	std::vector<LLPointer<LLViewerTexture> > mTextureList;
+
 	LLColor4U mGlowColor;
 	S32 mDebugColor;
 	const LLMatrix4* mTextureMatrix;
@@ -684,7 +686,7 @@ class LLVolumeGeometryManager: public LLGeometryManager
 	virtual void rebuildGeom(LLSpatialGroup* group);
 	virtual void rebuildMesh(LLSpatialGroup* group);
 	virtual void getGeometry(LLSpatialGroup* group);
-	void genDrawInfo(LLSpatialGroup* group, U32 mask, std::vector<LLFace*>& faces, BOOL distance_sort = FALSE);
+	void genDrawInfo(LLSpatialGroup* group, U32 mask, std::vector<LLFace*>& faces, BOOL distance_sort = FALSE, BOOL batch_textures = FALSE);
 	void registerFace(LLSpatialGroup* group, LLFace* facep, U32 type);
 };
 
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index b818da205eaade022ead9658955b21d79434b092..29f3acdf91e703e7b2001bb9d5fa1dfe6ed304c2 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -354,6 +354,7 @@ void LLViewerShaderMgr::setShaders()
 
 	//setup preprocessor definitions
 	LLShaderMgr::instance()->mDefinitions["samples"] = llformat("%d", gGLManager.getNumFBOFSAASamples(gSavedSettings.getU32("RenderFSAASamples")));
+	LLShaderMgr::instance()->mDefinitions["NUM_TEX_UNITS"] = llformat("%d", gGLManager.mNumTextureImageUnits);
 
 	reentrance = true;
 
@@ -941,6 +942,20 @@ BOOL LLViewerShaderMgr::loadShadersEffects()
 
 }
 
+void setup_indexed_texture(LLGLSLShader& shader)
+{
+	shader.bind();
+	shader.uniform1i("tex0", 0);
+	shader.uniform1i("tex1", 1);
+	shader.uniform1i("tex2", 2);
+	shader.uniform1i("tex3", 3);
+	shader.uniform1i("tex4", 4);
+	shader.uniform1i("tex5", 5);
+	shader.uniform1i("tex6", 6);
+	shader.uniform1i("tex7", 7);
+	shader.unbind();
+}
+
 BOOL LLViewerShaderMgr::loadShadersDeferred()
 {
 	if (mVertexShaderLevel[SHADER_DEFERRED] == 0)
@@ -992,9 +1007,14 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		gDeferredDiffuseProgram.mName = "Deferred Diffuse Shader";
 		gDeferredDiffuseProgram.mShaderFiles.clear();
 		gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER_ARB));
-		gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseF.glsl", GL_FRAGMENT_SHADER_ARB));
+		gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseIndexedF.glsl", GL_FRAGMENT_SHADER_ARB));
 		gDeferredDiffuseProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED];
 		success = gDeferredDiffuseProgram.createShader(NULL, NULL);
+
+		if (success)
+		{ //force tex0-7 to appropriate texture channels
+			setup_indexed_texture(gDeferredDiffuseProgram);
+		}
 	}
 
 	if (success)
@@ -1231,6 +1251,11 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		gDeferredFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER_ARB));
 		gDeferredFullbrightProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED];
 		success = gDeferredFullbrightProgram.createShader(NULL, NULL);
+
+		if (success)
+		{
+			setup_indexed_texture(gDeferredFullbrightProgram);
+		}
 	}
 
 	if (success)
diff --git a/indra/newview/llvopartgroup.cpp b/indra/newview/llvopartgroup.cpp
index 6f354b78b1e1fa953e105dc98fd1e6330cd9e3d7..85b740d819190e6655469a750bbf9fa24fec51a2 100644
--- a/indra/newview/llvopartgroup.cpp
+++ b/indra/newview/llvopartgroup.cpp
@@ -418,6 +418,7 @@ void LLParticlePartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_co
 			mFaceList.push_back(facep);
 			vertex_count += facep->getGeomCount();
 			index_count += facep->getIndicesCount();
+			llassert(facep->getIndicesCount() < 65536);
 		}
 		
 		obj->mDepth /= count;
diff --git a/indra/newview/llvosurfacepatch.cpp b/indra/newview/llvosurfacepatch.cpp
index dbcd4f50cadf6549509afcd1aee89ee4dd79601d..510525259f4c9a6f64fee619aafb1785880adf12 100644
--- a/indra/newview/llvosurfacepatch.cpp
+++ b/indra/newview/llvosurfacepatch.cpp
@@ -375,6 +375,8 @@ void LLVOSurfacePatch::updateMainGeometry(LLFace *facep,
 	S32 num_vertices, num_indices;
 	U32 index;
 
+	llassert(mLastStride > 0);
+
 	render_stride = mLastStride;
 	patch_size = mPatchp->getSurface()->getGridsPerPatchEdge();
 	S32 vert_size = patch_size / render_stride;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index cc443d32fb84298e7a69864ae040a1c470c0f7c8..87de064ad1152a6e222c428ddfa5bcf0b3e3c90c 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -1247,17 +1247,7 @@ BOOL LLVOVolume::calcLOD()
 	{
 		//setDebugText(llformat("%.2f:%.2f, %d", debug_distance, radius, cur_detail));
 
-		F32 bin_radius = getBinRadius();
-		F32 node_size = 0.f; 
-
-		LLSpatialGroup* group = mDrawable->getSpatialGroup();
-		if (group)
-		{
-			LLSpatialGroup::OctreeNode* node = group->mOctreeNode;
-			node_size = node->getSize()[0];
-		}
-
-		setDebugText(llformat("%.2f:%.2f", bin_radius, node_size));
+		setDebugText(llformat("%d", mDrawable->getFace(0)->getTextureIndex()));
 	}
 
 	if (cur_detail != mLOD)
@@ -3734,6 +3724,21 @@ LLVolumeBridge::LLVolumeBridge(LLDrawable* drawablep)
 	mSlopRatio = 0.25f;
 }
 
+bool can_batch_texture(LLFace* facep)
+{
+	if (facep->getTextureEntry()->getBumpmap())
+	{ //bump maps aren't worked into texture batching yet
+		return false;
+	}
+
+	if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE)
+	{ //texture animation breaks batches
+		return false;
+	}
+	
+	return true;
+}
+
 void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, U32 type)
 {
 	LLMemType mt(LLMemType::MTYPE_SPACE_PARTITION);
@@ -3784,12 +3789,36 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
 	
 	LLViewerTexture* tex = facep->getTexture();
 
+	U8 index = facep->getTextureIndex();
+
+	bool batchable = false;
+
+	if (index < 255 && idx >= 0)
+	{
+		if (index < draw_vec[idx]->mTextureList.size())
+		{
+			if (draw_vec[idx]->mTextureList[index].isNull())
+			{
+				batchable = true;
+				draw_vec[idx]->mTextureList[index] = tex;
+			}
+			else if (draw_vec[idx]->mTextureList[index] == tex)
+			{ //this face's texture index can be used with this batch
+				batchable = true;
+			}
+		}
+		else
+		{ //texture list can be expanded to fit this texture index
+			batchable = true;
+		}
+	}
+	
 	U8 glow = (U8) (facep->getTextureEntry()->getGlow() * 255);
 
 	if (idx >= 0 && 
 		draw_vec[idx]->mVertexBuffer == facep->getVertexBuffer() &&
 		draw_vec[idx]->mEnd == facep->getGeomIndex()-1 &&
-		(LLPipeline::sTextureBindTest || draw_vec[idx]->mTexture == tex) &&
+		(LLPipeline::sTextureBindTest || draw_vec[idx]->mTexture == tex || batchable) &&
 #if LL_DARWIN
 		draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange &&
 		draw_vec[idx]->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange &&
@@ -3803,6 +3832,12 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
 		draw_vec[idx]->mCount += facep->getIndicesCount();
 		draw_vec[idx]->mEnd += facep->getGeomCount();
 		draw_vec[idx]->mVSize = llmax(draw_vec[idx]->mVSize, facep->getVirtualSize());
+
+		if (index >= draw_vec[idx]->mTextureList.size())
+		{
+			draw_vec[idx]->mTextureList.resize(index+1);
+			draw_vec[idx]->mTextureList[index] = tex;
+		}
 		draw_vec[idx]->validate();
 		update_min_max(draw_vec[idx]->mExtents[0], draw_vec[idx]->mExtents[1], facep->mExtents[0]);
 		update_min_max(draw_vec[idx]->mExtents[0], draw_vec[idx]->mExtents[1], facep->mExtents[1]);
@@ -3833,6 +3868,11 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
 			draw_info->mDrawMode = LLRender::TRIANGLE_STRIP;
 		}
 
+		if (index < 255)
+		{ //initialize texture list for texture batching
+			draw_info->mTextureList.resize(index+1);
+			draw_info->mTextureList[index] = tex;
+		}
 		draw_info->validate();
 	}
 }
@@ -4258,11 +4298,16 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 	if (LLPipeline::sRenderDeferred)
 	{
 		bump_mask |= LLVertexBuffer::MAP_BINORMAL;
+		genDrawInfo(group, simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, simple_faces, FALSE, TRUE);
+		genDrawInfo(group, fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, fullbright_faces, FALSE, TRUE);
+		genDrawInfo(group, bump_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, bump_faces, FALSE, TRUE);
+	}
+	else
+	{
+		genDrawInfo(group, simple_mask, simple_faces);
+		genDrawInfo(group, fullbright_mask, fullbright_faces);
+		genDrawInfo(group, bump_mask, bump_faces, FALSE, TRUE);
 	}
-	
-	genDrawInfo(group, simple_mask, simple_faces);
-	genDrawInfo(group, bump_mask, bump_faces);
-	genDrawInfo(group, fullbright_mask, fullbright_faces);
 	genDrawInfo(group, alpha_mask, alpha_faces, TRUE);
 
 	if (!LLPipeline::sDelayVBUpdate)
@@ -4376,7 +4421,7 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 	llassert(!group || !group->isState(LLSpatialGroup::NEW_DRAWINFO));
 }
 
-void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::vector<LLFace*>& faces, BOOL distance_sort)
+void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::vector<LLFace*>& faces, BOOL distance_sort, BOOL batch_textures)
 {
 	//calculate maximum number of vertices to store in a single buffer
 	U32 max_vertices = (gSavedSettings.getS32("RenderMaxVBOSize")*1024)/LLVertexBuffer::calcVertexSize(group->mSpatialPartition->mVertexDataMask);
@@ -4435,19 +4480,71 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::
 		std::vector<LLFace*>::iterator i = face_iter;
 		++i;
 		
-		while (i != faces.end() && 
-			(LLPipeline::sTextureBindTest || (distance_sort || (*i)->getTexture() == tex)))
+		std::vector<LLViewerTexture*> texture_list;
+
+		if (!distance_sort && batch_textures)
 		{
-			facep = *i;
-			
-			if (geom_count + facep->getGeomCount() > max_vertices)
-			{ //cut batches on geom count too big
-				break;
+			U8 cur_tex = 0;
+			facep->setTextureIndex(cur_tex);
+			texture_list.push_back(tex);
+
+			if (can_batch_texture(facep))
+			{
+				while (i != faces.end())
+				{
+					facep = *i;
+					if (facep->getTexture() != tex)
+					{
+						cur_tex++;
+						if (cur_tex >= 8)
+						{ //cut batches on every 8 textures
+							break;
+						}
+						tex = facep->getTexture();
+						texture_list.push_back(tex);
+					}
+
+					if (geom_count + facep->getGeomCount() > max_vertices)
+					{ //cut batches on geom count too big
+						break;
+					}
+
+					if (!can_batch_texture(facep))
+					{ //cut batches on things that require single texture rendering (animated texture, bump maps)
+						break;
+					}
+					
+					++i;
+					index_count += facep->getIndicesCount();
+					geom_count += facep->getGeomCount();
+
+					facep->setTextureIndex(cur_tex);
+				}
 			}
 
-			++i;
-			index_count += facep->getIndicesCount();
-			geom_count += facep->getGeomCount();
+			tex = texture_list[0];
+		}
+		else
+		{
+			while (i != faces.end() && 
+				(LLPipeline::sTextureBindTest || (distance_sort || (*i)->getTexture() == tex)))
+			{
+				facep = *i;
+			
+
+				//face has no texture index
+				facep->mDrawInfo = NULL;
+				facep->setTextureIndex(255);
+
+				if (geom_count + facep->getGeomCount() > max_vertices)
+				{ //cut batches on geom count too big
+					break;
+				}
+
+				++i;
+				index_count += facep->getIndicesCount();
+				geom_count += facep->getGeomCount();
+			}
 		}
 	
 		//create/delete/resize vertex buffer if needed
@@ -4497,6 +4594,11 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, std::
 			facep->setGeomIndex(index_offset);
 			facep->setVertexBuffer(buffer);	
 			
+			if (batch_textures && facep->getTextureIndex() == 255)
+			{
+				llerrs << "Invalid texture index." << llendl;
+			}
+			
 			{
 				//for debugging, set last time face was updated vs moved
 				facep->updateRebuildFlags();
@@ -4695,7 +4797,7 @@ void LLGeometryManager::addGeometryCount(LLSpatialGroup* group, U32 &vertex_coun
 			{
 				vertex_count += facep->getGeomCount();
 				index_count += facep->getIndicesCount();
-
+				llassert(facep->getIndicesCount() < 65536);
 				//remember face (for sorting)
 				mFaceList.push_back(facep);
 			}