//===== Copyright © 1996-2007, Valve Corporation, All rights reserved. ======//
//
// Purpose: 
//
// $NoKeywords: $
//
//===========================================================================//


#include "render_pch.h"
#include "brushbatchrender.h"
#include "icliententity.h"
#include "r_decal.h"
#include "gl_rsurf.h"
#include "tier0/vprof.h"
#include <algorithm>


CBrushBatchRender g_BrushBatchRenderer;

FORCEINLINE void ModulateMaterial( IMaterial *pMaterial, float *pOldColor )
{
	if ( g_bIsBlendingOrModulating )
	{
		pOldColor[3] = pMaterial->GetAlphaModulation( );
		pMaterial->GetColorModulation( &pOldColor[0], &pOldColor[1], &pOldColor[2] );
		pMaterial->AlphaModulate( r_blend );
		pMaterial->ColorModulate( r_colormod[0], r_colormod[1], r_colormod[2] );
	}

}

FORCEINLINE void UnModulateMaterial( IMaterial *pMaterial, float *pOldColor )
{
	if ( g_bIsBlendingOrModulating )
	{
		pMaterial->AlphaModulate( pOldColor[3] );
		pMaterial->ColorModulate( pOldColor[0], pOldColor[1], pOldColor[2] );
	}
}

int __cdecl CBrushBatchRender::SurfaceCmp(const surfacelist_t *s0, const surfacelist_t *s1 )
{
	int sortID0 = MSurf_MaterialSortID( s0->surfID );
	int sortID1 = MSurf_MaterialSortID( s1->surfID );

	return sortID0 - sortID1;
}


// Builds a transrender_t, then executes it's drawing commands
void CBrushBatchRender::DrawTranslucentBrushModel( IMatRenderContext *pRenderContext, model_t *model, IClientEntity *baseentity )
{
	transrender_t render;
	render.pLastBatch = NULL;
	render.pLastNode = NULL;
	render.nodeCount = 0;
	render.surfaceCount = 0;
	render.batchCount = 0;
	render.decalSurfaceCount = 0;
	BuildTransLists_r( render, model, model->brush.pShared->nodes + model->brush.firstnode );
	void *pProxyData = baseentity ? baseentity->GetClientRenderable() : NULL;
	DrawTransLists( pRenderContext, render, pProxyData );
}

void CBrushBatchRender::AddSurfaceToBatch( transrender_t &render, transnode_t *pNode, transbatch_t *pBatch, SurfaceHandle_t surfID )
{
	pBatch->surfaceCount++;
	Assert(render.surfaceCount < MAX_TRANS_SURFACES);

	pBatch->indexCount += (MSurf_VertCount( surfID )-2)*3;
	render.surfaces[render.surfaceCount] = surfID;
	render.surfaceCount++;
	if ( SurfaceHasDecals( surfID ) || MSurf_ShadowDecals( surfID ) != SHADOW_DECAL_HANDLE_INVALID )
	{
		Assert(render.decalSurfaceCount < MAX_TRANS_DECALS);
		pNode->decalSurfaceCount++;
		render.decalSurfaces[render.decalSurfaceCount] = surfID;
		render.decalSurfaceCount++;
	}
}

void CBrushBatchRender::AddTransNode( transrender_t &render )
{
	render.pLastNode = &render.nodes[render.nodeCount];
	render.nodeCount++;
	Assert(render.nodeCount < MAX_TRANS_NODES);
	render.pLastBatch = NULL;
	render.pLastNode->firstBatch = render.batchCount;
	render.pLastNode->firstDecalSurface = render.decalSurfaceCount;
	render.pLastNode->batchCount = 0;
	render.pLastNode->decalSurfaceCount = 0;
}

void CBrushBatchRender::AddTransBatch( transrender_t &render, SurfaceHandle_t surfID )
{
	transbatch_t &batch = render.batches[render.pLastNode->firstBatch + render.pLastNode->batchCount];
	Assert(render.batchCount < MAX_TRANS_BATCHES);
	render.pLastNode->batchCount++;
	render.batchCount++;
	batch.firstSurface = render.surfaceCount;
	batch.surfaceCount = 0;
	batch.pMaterial = MSurf_TexInfo( surfID )->material;
	batch.sortID = MSurf_MaterialSortID( surfID );
	batch.indexCount = 0;
	render.pLastBatch = &batch;
	AddSurfaceToBatch( render, render.pLastNode, &batch, surfID );
}

// build node lists
void CBrushBatchRender::BuildTransLists_r( transrender_t &render, model_t *model, mnode_t *node )
{
	float		dot;

	if (node->contents >= 0)
		return;

	// node is just a decision point, so go down the apropriate sides
	// find which side of the node we are on
	cplane_t *plane = node->plane;
	if ( plane->type <= PLANE_Z )
	{
		dot = modelorg[plane->type] - plane->dist;
	}
	else
	{
		dot = DotProduct (modelorg, plane->normal) - plane->dist;
	}

	int side = (dot >= 0) ? 0 : 1;

	// back side first - translucent surfaces need to render in back to front order
	// to appear correctly.
	BuildTransLists_r(render, model, node->children[!side]);

	// emit all surfaces on node
	CUtlVectorFixed<surfacelist_t, 256> sortList;
	SurfaceHandle_t surfID = SurfaceHandleFromIndex( node->firstsurface, model->brush.pShared );
	for ( int i = 0; i < node->numsurfaces; i++, surfID++ )
	{
		// skip opaque surfaces
		if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
		{
			if ( ((MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0) )
			{
				// Backface cull here, so they won't do any more work
				if ( ( side ^ !!(MSurf_Flags( surfID ) & SURFDRAW_PLANEBACK)) )
					continue;
			}

			// If this can be appended to the previous batch, do so
			int sortID = MSurf_MaterialSortID( surfID );
			if ( render.pLastBatch && render.pLastBatch->sortID == sortID )
			{
				AddSurfaceToBatch( render, render.pLastNode, render.pLastBatch, surfID );
			}
			else
			{
				// save it off for sorting, then a later append
				int sortIndex = sortList.AddToTail();
				sortList[sortIndex].surfID = surfID;
			}
		}
	}

	// We've got surfaces on this node that can't be added to the previous node
	if ( sortList.Count() )
	{
		// sort by material
		sortList.Sort( SurfaceCmp );

		// add a new sort group
		AddTransNode( render );
		int lastSortID = -1;
		// now add the optimal number of batches to that group
		for ( int i = 0; i < sortList.Count(); i++ )
		{
			surfID = sortList[i].surfID;
			int sortID = MSurf_MaterialSortID( surfID );
			if ( lastSortID == sortID )
			{
				// can be drawn in a single call with the current list of surfaces, append
				AddSurfaceToBatch( render, render.pLastNode, render.pLastBatch, surfID );
			}
			else
			{
				// requires a break (material/lightmap change).
				AddTransBatch( render, surfID );
				lastSortID = sortID;
			}
		}

		// don't batch across decals or decals will sort incorrectly
		if ( render.pLastNode->decalSurfaceCount )
		{
			render.pLastNode = NULL;
			render.pLastBatch = NULL;
		}
	}

	// front side
	BuildTransLists_r(render, model, node->children[side]);
}

void CBrushBatchRender::DrawTransLists( IMatRenderContext *pRenderContext, transrender_t &render, void *pProxyData )
{
	PIXEVENT( pRenderContext, "DrawTransLists()" );

	bool skipLight = false;
	if ( g_pMaterialSystemConfig->nFullbright == 1 )
	{
		pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );
		skipLight = true;
	}

	float pOldColor[4];


	for ( int i = 0; i < render.nodeCount; i++ )
	{
		int j;
		const transnode_t &node = render.nodes[i];
		for ( j = 0; j < node.batchCount; j++ )
		{
			const transbatch_t &batch = render.batches[node.firstBatch+j];

			CMeshBuilder meshBuilder;
			IMaterial *pMaterial = batch.pMaterial;

			ModulateMaterial( pMaterial, pOldColor );

			if ( !skipLight )
			{
				pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
			}
			pRenderContext->Bind( pMaterial, pProxyData );

			IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
			meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batch.indexCount );

			for ( int k = 0; k < batch.surfaceCount; k++ )
			{
				SurfaceHandle_t surfID = render.surfaces[batch.firstSurface + k];
				Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
				BuildIndicesForSurface( meshBuilder, surfID );

			}
			meshBuilder.End( false, true );

			// Don't leave the material in a bogus state
			UnModulateMaterial( pMaterial, pOldColor );
		}

		if ( node.decalSurfaceCount )
		{
			for ( j = 0; j < node.decalSurfaceCount; j++ )
			{
				SurfaceHandle_t surfID = render.decalSurfaces[node.firstDecalSurface + j];
				Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
				if( SurfaceHasDecals( surfID ) )
				{
					DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
				}

				// Add shadows too....
				ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
				if (decalHandle != SHADOW_DECAL_HANDLE_INVALID)
				{
					g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
				}
			}

			// Now draw the decals + shadows for this node
			// This order relative to the surfaces is important for translucency to work correctly.
			DecalSurfaceDraw(pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP);

			// FIXME: Decals are not being rendered while illuminated by the flashlight

			// FIXME: Need to draw decals in flashlight rendering mode
			// Retire decals on opaque world surfaces
			R_DecalFlushDestroyList();

			DecalSurfacesInit( true );

			// Draw all shadows on the brush
			g_pShadowMgr->RenderProjectedTextures( pRenderContext );
		}
		if ( g_ShaderDebug.anydebug )
		{
			CUtlVector<msurface2_t *> brushList;
			for ( j = 0; j < node.batchCount; j++ )
			{
				const transbatch_t &batch = render.batches[node.firstBatch+j];
				for ( int k = 0; k < batch.surfaceCount; k++ )
				{
					brushList.AddToTail( render.surfaces[batch.firstSurface + k] );
				}
			}
			DrawDebugInformation( pRenderContext, brushList.Base(), brushList.Count() );
		}
	}
}



//-----------------------------------------------------------------------------
// Purpose: This is used when the mat_dxlevel is changed to reset the brush
//          models.
//-----------------------------------------------------------------------------
void R_BrushBatchInit( void )
{
	g_BrushBatchRenderer.LevelInit();
}

void CBrushBatchRender::LevelInit()
{
	AUTO_LOCK( m_Mutex );

	unsigned short iNext;
	for( unsigned short i=m_renderList.Head(); i != m_renderList.InvalidIndex(); i=iNext )
	{
		iNext = m_renderList.Next(i);
		delete m_renderList.Element(i);
	}

	m_renderList.Purge();
	ClearRenderHandles();
}

void CBrushBatchRender::ClearRenderHandles( void )
{
	for ( int iBrush = 1 ; iBrush < host_state.worldbrush->numsubmodels ; ++iBrush )
	{
		char szBrushModel[5]; // inline model names "*1", "*2" etc
		Q_snprintf( szBrushModel, sizeof( szBrushModel ), "*%i", iBrush );
		model_t *pModel = modelloader->GetModelForName( szBrushModel, IModelLoader::FMODELLOADER_SERVER );	
		if ( pModel )
		{
			pModel->brush.renderHandle = 0;
		}
	}
}

// Create a compact, optimal list of rendering commands for the opaque parts of a brush model
// NOTE: This just skips translucent surfaces assuming a separate transrender_t pass!
CBrushBatchRender::brushrender_t *CBrushBatchRender::FindOrCreateRenderBatch( model_t *pModel )
{
	if ( !pModel->brush.nummodelsurfaces )
		return NULL;

	AUTO_LOCK( m_Mutex );

	unsigned short index = pModel->brush.renderHandle - 1;
	if ( m_renderList.IsValidIndex( index ) )
		return m_renderList.Element(index);

	brushrender_t *pRender = new brushrender_t;
	index = m_renderList.AddToTail( pRender );
	pModel->brush.renderHandle = index + 1;
	brushrender_t &render = *pRender; 

	render.pPlanes = NULL;
	render.pMeshes = NULL;
	render.planeCount = 0;
	render.meshCount = 0;
	render.totalIndexCount = 0;
	render.totalVertexCount = 0;

	CUtlVector<cplane_t *>	planeList;
	CUtlVector<surfacelist_t> surfaceList;

	int i;

	SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
	for (i=0 ; i<pModel->brush.nummodelsurfaces; i++, surfID++)
	{
		// UNDONE: For now, just draw these in a separate pass
		if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
			continue;

#ifndef _PS3
		cplane_t *plane = surfID->plane;
#else
		cplane_t *plane = &surfID->m_plane;
#endif

		int planeIndex = planeList.Find(plane);
		if ( planeIndex == -1 )
		{
			planeIndex = planeList.AddToTail( plane );
		}
		surfacelist_t tmp;
		tmp.surfID = surfID;
		tmp.surfaceIndex = i;
		tmp.planeIndex = planeIndex;
		surfaceList.AddToTail( tmp );
	}
	surfaceList.Sort( SurfaceCmp );
	render.pPlanes = new cplane_t *[planeList.Count()];
	render.planeCount = planeList.Count();
	memcpy( render.pPlanes, planeList.Base(), sizeof(cplane_t *)*planeList.Count() );
	render.pSurfaces = new brushrendersurface_t[surfaceList.Count()];
	render.surfaceCount = surfaceList.Count();

	int meshCount = 0;
	int batchCount = 0;
	int lastSortID = -1;
	IMesh *pLastMesh = NULL;

	brushrendermesh_t *pMesh = NULL;
	brushrendermesh_t tmpMesh[MAX_VERTEX_FORMAT_CHANGES];
	brushrenderbatch_t *pBatch = NULL;
	brushrenderbatch_t tmpBatch[128];

	for ( i = 0; i < surfaceList.Count(); i++ )
	{
		render.pSurfaces[i].surfaceIndex = surfaceList[i].surfaceIndex;
		render.pSurfaces[i].planeIndex = surfaceList[i].planeIndex;

		surfID = surfaceList[i].surfID;
		int sortID = MSurf_MaterialSortID( surfID );
		if ( g_WorldStaticMeshes[sortID] != pLastMesh )
		{
			pMesh = tmpMesh + meshCount;
			pMesh->firstBatch = batchCount;
			pMesh->batchCount = 0;
			lastSortID = -1; // force a new batch
			meshCount++;
		}
		if ( sortID != lastSortID )
		{
			pBatch = tmpBatch + batchCount;
			pBatch->firstSurface = i;
			pBatch->surfaceCount = 0;
			pBatch->sortID = sortID;
			pBatch->pMaterial = MSurf_TexInfo( surfID )->material;
			pBatch->indexCount = 0;
			pMesh->batchCount++;
			batchCount++;
		}
		pLastMesh = g_WorldStaticMeshes[sortID];
		lastSortID = sortID;
		pBatch->surfaceCount++;
		int vertCount = MSurf_VertCount( surfID );
		int indexCount = (vertCount - 2) * 3;
		pBatch->indexCount += indexCount;
		render.totalIndexCount += indexCount;
		render.totalVertexCount += vertCount;
	}

	render.pMeshes = new brushrendermesh_t[meshCount];
	memcpy( render.pMeshes, tmpMesh, sizeof(brushrendermesh_t) * meshCount );
	render.meshCount = meshCount;
	render.pBatches = new brushrenderbatch_t[batchCount];
	memcpy( render.pBatches, tmpBatch, sizeof(brushrenderbatch_t) * batchCount );
	render.batchCount = batchCount;
	return &render;
}


//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawOpaqueBrushModel( IMatRenderContext *pRenderContext, IClientEntity *baseentity, model_t *model, ERenderDepthMode_t DepthMode )
{
	VPROF( "R_DrawOpaqueBrushModel" );
	SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( model->brush.firstmodelsurface, model->brush.pShared );

	brushrender_t *pRender = FindOrCreateRenderBatch( model );
	int i;
	if ( !pRender )
		return;

	bool skipLight = false;

	PIXEVENT( pRenderContext, "DrawOpaqueBrushModel()" );

	if ( (g_pMaterialSystemConfig->nFullbright == 1) || ( DepthMode == DEPTH_MODE_SHADOW || DepthMode == DEPTH_MODE_SSA0 ) )
	{
		pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );
		skipLight = true;
	}

	void *pProxyData = baseentity ? baseentity->GetClientRenderable() : NULL;
	int backface[1024];
	Assert( pRender->planeCount < 1024 );

	// NOTE: Backface culling is almost no perf gain.  Can be removed from brush model rendering.
	// Check the shared planes once
	if ( DepthMode == DEPTH_MODE_SHADOW || DepthMode == DEPTH_MODE_SSA0 )
	{
		for ( i = 0; i < pRender->planeCount; i++ )
		{
			backface[i] = 0;
		}
	}
	else
	{
		for ( i = 0; i < pRender->planeCount; i++ )
		{
			float dot = DotProduct( modelorg, pRender->pPlanes[i]->normal) - pRender->pPlanes[i]->dist;
			backface[i] = ( dot < BACKFACE_EPSILON ) ? true : false; // don't backface cull when rendering to shadow map
		}
	}

	float pOldColor[4];

	// PORTAL 2 PAINT RENDERING
	CUtlVectorFixedGrowable< SurfaceHandle_t, 64 > paintableSurfaces;
	CUtlVectorFixedGrowable< int, 16 > batchPaintableSurfaceCount;
	CUtlVectorFixedGrowable< int, 16 > batchPaintableSurfaceIndexCount;

	for ( i = 0; i < pRender->meshCount; i++ )
	{
		brushrendermesh_t &mesh = pRender->pMeshes[i];
		for ( int j = 0; j < mesh.batchCount; j++ )
		{
			int nBatchIndex = batchPaintableSurfaceCount.Count();
			batchPaintableSurfaceCount.AddToTail( 0 );
			batchPaintableSurfaceIndexCount.AddToTail( 0 );

			brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];

			int k;
			for ( k = 0; k < batch.surfaceCount; k++ )
			{
				brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
				if ( !backface[surface.planeIndex] )
					break;
			}

			if ( k == batch.surfaceCount )
				continue;

			CMeshBuilder meshBuilder;
			IMaterial *pMaterial = NULL;

			if ( DepthMode != DEPTH_MODE_NORMAL )
			{
				// Select proper override material
				int nAlphaTest = (int) batch.pMaterial->IsAlphaTested();
				int nNoCull = (int) batch.pMaterial->IsTwoSided();
				IMaterial *pDepthWriteMaterial;

				if ( DepthMode == DEPTH_MODE_SHADOW )
				{
					pDepthWriteMaterial = g_pMaterialDepthWrite[ nAlphaTest ][ nNoCull ];
				}
				else
				{
					pDepthWriteMaterial = g_pMaterialSSAODepthWrite[ nAlphaTest ][ nNoCull ];
				}

				if ( nAlphaTest == 1 )
				{
					static unsigned int originalTextureVarCache = 0;
					IMaterialVar *pOriginalTextureVar = batch.pMaterial->FindVarFast( "$basetexture", &originalTextureVarCache );
					static unsigned int originalTextureFrameVarCache = 0;
					IMaterialVar *pOriginalTextureFrameVar = batch.pMaterial->FindVarFast( "$frame", &originalTextureFrameVarCache );
					static unsigned int originalAlphaRefCache = 0;
					IMaterialVar *pOriginalAlphaRefVar = batch.pMaterial->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache );

					static unsigned int textureVarCache = 0;
					IMaterialVar *pTextureVar = pDepthWriteMaterial->FindVarFast( "$basetexture", &textureVarCache );
					static unsigned int textureFrameVarCache = 0;
					IMaterialVar *pTextureFrameVar = pDepthWriteMaterial->FindVarFast( "$frame", &textureFrameVarCache );
					static unsigned int alphaRefCache = 0;
					IMaterialVar *pAlphaRefVar = pDepthWriteMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache );

					if( pTextureVar && pOriginalTextureVar )
					{
						pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() );
					}

					if( pTextureFrameVar && pOriginalTextureFrameVar )
					{
						pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() );
					}

					if( pAlphaRefVar && pOriginalAlphaRefVar )
					{
						pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() );
					}
				}

				pMaterial = pDepthWriteMaterial;
			}
			else
			{
				pMaterial = batch.pMaterial;

				// Store off the old color + alpha
				ModulateMaterial( pMaterial, pOldColor );
				if ( !skipLight )
				{
					pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
				}
			}

			pRenderContext->Bind( pMaterial, pProxyData );
			IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
			meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batch.indexCount );

			for ( ; k < batch.surfaceCount; k++ )
			{
				brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
				if ( backface[surface.planeIndex] )
					continue;
				SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;

				Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );

				// PORTAL 2 PAINT RENDERING
				if ( (DepthMode == DEPTH_MODE_NORMAL) && ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED ) )
				{
					paintableSurfaces.AddToTail( surfID );
					++ batchPaintableSurfaceCount[ nBatchIndex ];

					int nVertCount, nIndexCount;

					Shader_GetSurfVertexAndIndexCount( surfID, &nVertCount, &nIndexCount );
					batchPaintableSurfaceIndexCount[ nBatchIndex ] += nIndexCount;
				}
				BuildIndicesForSurface( meshBuilder, surfID );

				if( SurfaceHasDecals( surfID ) && (DepthMode == DEPTH_MODE_NORMAL))
				{
					DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
				}

				// Add overlay fragments to list.
				// FIXME: A little code support is necessary to get overlays working on brush models
				//	OverlayMgr()->AddFragmentListToRenderList( MSurf_OverlayFragmentList( surfID ), false );

				if ( DepthMode == DEPTH_MODE_NORMAL )
				{
					// Add render-to-texture shadows too....
					ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
					if (decalHandle != SHADOW_DECAL_HANDLE_INVALID)
					{
						g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
					}
				}
			}

			meshBuilder.End( false, true );

			if ( DepthMode == DEPTH_MODE_NORMAL )
			{
				// Don't leave the material in a bogus state
				UnModulateMaterial( pMaterial, pOldColor );
			}
		}
	}

	if ( DepthMode != DEPTH_MODE_NORMAL )
		return;

	// PORTAL 2 PAINT RENDERING
	if ( paintableSurfaces.Count() )
	{
		pRenderContext->SetRenderingPaint( true );
		PIXEVENT( pRenderContext, "Paint" );

		int nBatchIndex = 0;
		int nSurfaceIndex = 0;
		for ( i = 0; i < pRender->meshCount; i++ )
		{
			brushrendermesh_t &mesh = pRender->pMeshes[i];
			for ( int j = 0; j < mesh.batchCount; j++ )
			{
				int nSurfaceCount = batchPaintableSurfaceCount[ nBatchIndex ];
				if ( nSurfaceCount > 0 )
				{
					brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];

					IMaterial *pMaterial = batch.pMaterial;

					if ( !skipLight )
					{
						pRenderContext->BindLightmapPage( materialSortInfoArray[batch.sortID].lightmapPageID );
					}

					pRenderContext->Bind( pMaterial, pProxyData );

					CMeshBuilder meshBuilder;
					IMesh *pBuildMesh = pRenderContext->GetDynamicMesh( false, g_WorldStaticMeshes[batch.sortID], NULL, NULL );
					meshBuilder.Begin( pBuildMesh, MATERIAL_TRIANGLES, 0, batchPaintableSurfaceIndexCount[ nBatchIndex ] );

					for ( int i = 0; i < nSurfaceCount; ++ i, ++ nSurfaceIndex )
					{
						BuildIndicesForSurface( meshBuilder, paintableSurfaces[ nSurfaceIndex ] );
					}

					meshBuilder.End( false, true );
				}

				++ nBatchIndex;
			}
		}
		pRenderContext->SetRenderingPaint( false );
	}

	if ( g_ShaderDebug.anydebug )
	{
		for ( i = 0; i < pRender->meshCount; i++ )
		{
			brushrendermesh_t &mesh = pRender->pMeshes[i];
			CUtlVector<msurface2_t *> brushList;
			for ( int j = 0; j < mesh.batchCount; j++ )
			{
				brushrenderbatch_t &batch = pRender->pBatches[mesh.firstBatch + j];
				for ( int k = 0; k < batch.surfaceCount; k++ )
				{
					brushrendersurface_t &surface = pRender->pSurfaces[batch.firstSurface + k];
					if ( backface[surface.planeIndex] )
						continue;
					SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
					brushList.AddToTail(surfID);
				}
			}
			// now draw debug for each drawn surface
			DrawDebugInformation( pRenderContext, brushList.Base(), brushList.Count() );
		}
	}
}

//-----------------------------------------------------------------------------
// Draws an translucent (sorted) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawTranslucentBrushModel( IMatRenderContext *pRenderContext, IClientEntity *baseentity, model_t *model, ERenderDepthMode_t DepthMode, bool bDrawOpaque, bool bDrawTranslucent )
{
	if ( bDrawOpaque )
	{
		DrawOpaqueBrushModel( pRenderContext, baseentity, model, DepthMode );
	}

	if ( ( DepthMode == DEPTH_MODE_NORMAL ) && bDrawTranslucent )
	{
		DrawTranslucentBrushModel( pRenderContext, model, baseentity );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Draws a brush model shadow for render-to-texture shadows
//-----------------------------------------------------------------------------
// UNDONE: This is reasonable, but it could be much faster as follows:
// Build a vertex buffer cache.  A block-allocated static mesh with 1024 verts
// per block or something.
// When a new brush is encountered, fill it in to the current block or the 
// next one (first fit allocator).  Then this routine could simply draw
// a static mesh with a single index buffer build, draw call (no dynamic vb).
void CBrushBatchRender::DrawBrushModelShadow( IMatRenderContext *pRenderContext, model_t *model, IClientRenderable *pRenderable )
{
	brushrender_t *pRender = FindOrCreateRenderBatch( (model_t *)model );
	if ( !pRender )
		return;

	pRenderContext->Bind( g_pMaterialShadowBuild, pRenderable );

	// Draws all surfaces in the brush model in arbitrary order
	SurfaceHandle_t surfID = SurfaceHandleFromIndex( model->brush.firstmodelsurface, model->brush.pShared );
	IMesh *pMesh = pRenderContext->GetDynamicMesh();
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, pRender->totalVertexCount, pRender->totalIndexCount );

	for ( int i=0 ; i<model->brush.nummodelsurfaces ; i++, surfID++)
	{
		Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );

		if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
			continue;

		int startVert = MSurf_FirstVertIndex( surfID );
		int vertCount = MSurf_VertCount( surfID );
		int startIndex = meshBuilder.GetCurrentVertex();
		int j;
		for ( j = 0; j < vertCount; j++ )
		{
			int vertIndex = model->brush.pShared->vertindices[startVert + j];

			// world-space vertex
			meshBuilder.Position3fv( model->brush.pShared->vertexes[vertIndex].position.Base() );
			meshBuilder.TexCoord2f( 0, 0.0f, 0.0f );
			meshBuilder.AdvanceVertex();
		}

		for ( j = 0; j < vertCount-2; j++ )
		{
			meshBuilder.FastIndex( startIndex );
			meshBuilder.FastIndex( startIndex + j + 1 );
			meshBuilder.FastIndex( startIndex + j + 2 );
		}
	}
	meshBuilder.End();
	pMesh->Draw();
}



inline bool __cdecl CBrushBatchRender::BatchSortLessFunc( const BrushBatchRenderData_t &left, const BrushBatchRenderData_t &right )
{
	brushrenderbatch_t &leftBatch = left.m_pBrushRender->pBatches[ left.m_nBatchIndex ];
	brushrenderbatch_t &rightBatch = right.m_pBrushRender->pBatches[ right.m_nBatchIndex ];

	if ( left.m_pMaterial != right.m_pMaterial )
		return left.m_pMaterial < right.m_pMaterial;
	if ( leftBatch.sortID != rightBatch.sortID )
		return leftBatch.sortID < rightBatch.sortID;
	if ( left.m_pInstanceData->m_pStencilState != right.m_pInstanceData->m_pStencilState )
		return left.m_pInstanceData->m_pStencilState < right.m_pInstanceData->m_pStencilState;
	if ( left.m_pInstanceData->m_pBrushToWorld != right.m_pInstanceData->m_pBrushToWorld )
		return left.m_pInstanceData->m_pBrushToWorld < right.m_pInstanceData->m_pBrushToWorld;
	return false;
}


//-----------------------------------------------------------------------------
// Builds the lists of stuff to draw
//-----------------------------------------------------------------------------
void CBrushBatchRender::BuildBatchListToDraw( int nCount, const BrushArrayInstanceData_t *pInstanceData, 
	CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > &batchesToRender, brushrender_t **ppBrushRender )
{
	for ( int i = 0; i < nCount; ++i )
	{
		const BrushArrayInstanceData_t &instance = pInstanceData[i];
		brushrender_t *pRender = g_BrushBatchRenderer.FindOrCreateRenderBatch( const_cast< model_t* >( instance.m_pBrushModel ) );
		ppBrushRender[i] = pRender;
		if ( !pRender )
			continue;

		for ( int m = 0; m < pRender->meshCount; ++m )
		{
			brushrendermesh_t &mesh = pRender->pMeshes[m];
			int nBatchIndex = mesh.firstBatch;
			for ( int j = 0; j < mesh.batchCount; ++j, ++nBatchIndex )
			{
				int nIndex = batchesToRender.AddToTail();
				BrushBatchRenderData_t &batch = batchesToRender[nIndex];
				batch.m_pMaterial = pRender->pBatches[nBatchIndex].pMaterial;
				batch.m_pInstanceData = &instance;
				batch.m_pBrushRender = pRender;
				batch.m_nBatchIndex = nBatchIndex;
				Assert( nBatchIndex < ( 1 << 15 ) );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Computes lightmap pages
//-----------------------------------------------------------------------------
void CBrushBatchRender::ComputeLightmapPages( int nCount, BrushBatchRenderData_t *pRenderData )
{
	if ( g_pMaterialSystemConfig->nFullbright != 1 )
	{
		for ( int i = 0; i < nCount; ++i )
		{
			brushrenderbatch_t &batch = pRenderData[i].m_pBrushRender->pBatches[ pRenderData[i].m_nBatchIndex ];
			int nSortID = batch.sortID;
			Assert( nSortID >= 0 && nSortID < g_WorldStaticMeshes.Count() );
			pRenderData[i].m_nLightmapPage = materialSortInfoArray[ nSortID ].lightmapPageID;
		}
		return;
	}

	// Fullbright case. Bump works for unbumped surfaces too
	for ( int i = 0; i < nCount; ++i )
	{
		pRenderData[i].m_nLightmapPage = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP;
	}
}


//-----------------------------------------------------------------------------
// Computes the # of indices in an instance group
//-----------------------------------------------------------------------------
int CBrushBatchRender::ComputeInstanceGroups( IMatRenderContext *pRenderContext, int nCount, BrushBatchRenderData_t *pRenderData, CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > &instanceGroups )
{
	int nMaxIndices = pRenderContext->GetMaxIndicesToRender();

	int nMaxInstanceCount = 0;
	IMaterial *pLastMaterial = NULL;
	IMaterial *pLastActualMaterial = NULL;
	BrushBatchRenderData_t *pFirstInstance = NULL;
	int nInstanceCount = 0;
	int nIndexCount = 0;
	for ( int i = 0; i < nCount; i++ )
	{
		BrushBatchRenderData_t &renderData = pRenderData[i];
		brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];

		int nNextIndexCount = nIndexCount + batch.indexCount;

		// Fire it away if the material changes	or we overflow index count
		if ( ( pLastMaterial != batch.pMaterial ) || ( pLastMaterial != pLastActualMaterial ) || ( nNextIndexCount > nMaxIndices ) )
		{
			if ( nInstanceCount > 0 )
			{
				int nIndex = instanceGroups.AddToTail();
				instanceGroups[nIndex].m_pRenderData = pFirstInstance;
				instanceGroups[nIndex].m_nCount = nInstanceCount;
				instanceGroups[nIndex].m_nIndexCount = nIndexCount;
				instanceGroups[nIndex].m_pMaterial = pLastMaterial;
				instanceGroups[nIndex].m_pActualMaterial = pLastActualMaterial;
				if ( nInstanceCount > nMaxInstanceCount )
				{
					nMaxInstanceCount = nInstanceCount;
				}
			}
			nInstanceCount = 0;
			pFirstInstance = &renderData;
			nIndexCount = batch.indexCount;
			pLastMaterial = renderData.m_pMaterial;

			// This is necessary for shadow depth rendering. We need to re-bind
			// for every alpha tested material
			pLastActualMaterial = renderData.m_nIsAlphaTested ? batch.pMaterial : pLastMaterial;
		}
		else
		{
			nIndexCount = nNextIndexCount;
		}

		++nInstanceCount;
	}

	if ( nInstanceCount > 0 )
	{
		int nIndex = instanceGroups.AddToTail();
		instanceGroups[nIndex].m_pRenderData = pFirstInstance;
		instanceGroups[nIndex].m_nCount = nInstanceCount;
		instanceGroups[nIndex].m_nIndexCount = nIndexCount;
		instanceGroups[nIndex].m_pMaterial = pLastMaterial;
		instanceGroups[nIndex].m_pActualMaterial = pLastActualMaterial;
		if ( nInstanceCount > nMaxInstanceCount )
		{
			nMaxInstanceCount = nInstanceCount;
		}
	}

	return nMaxInstanceCount;
}


//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
bool CBrushBatchRender::DrawSortedBatchList( IMatRenderContext* pRenderContext, int nCount, BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
	VPROF( "DrawSortedBatchList" );
	PIXEVENT( pRenderContext, "DrawSortedBatchList()" );

	// Needed to allow the system to detect which samplers are bound to lightmap textures
	pRenderContext->BindLightmapPage( 0 );

	MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );

	bool bHasPaintedSurfaces = false;
	for ( int i = 0; i < nCount; i++ )
	{
		BrushInstanceGroup_t &group = pInstanceGroup[i];

		pRenderContext->Bind( group.m_pMaterial, NULL );

		// Only writing indices
		// FIXME: Can we make this a static index buffer?
		IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
		CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
		indexBuilder.Lock( group.m_nIndexCount, 0 );
		int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);

		group.m_nHasPaintedSurfaces = false;
		for ( int j = 0; j < group.m_nCount; ++j )
		{
			BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
			const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
			renderData.m_nHasPaintedSurfaces = false;
			const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
			SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
			for ( int k = 0; k < batch.surfaceCount; k++ )
			{
				const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
				SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;

				Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
				BuildIndicesForSurface( indexBuilder, surfID );
				if ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED )
				{
					group.m_nHasPaintedSurfaces = true;
					renderData.m_nHasPaintedSurfaces = true;
					bHasPaintedSurfaces = true;
				}
			}

			MeshInstanceData_t &instance = pInstance[ j ];
			instance.m_pEnvCubemap = NULL;
			instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
			instance.m_pLightingState = NULL;
			instance.m_nBoneCount = 1;
			instance.m_pBoneRemap = NULL;
			instance.m_nIndexOffset = nIndexOffset;
			instance.m_nIndexCount = batch.indexCount;
			instance.m_nPrimType = MATERIAL_TRIANGLES;
			instance.m_pColorBuffer = NULL;
			instance.m_nColorVertexOffsetInBytes = 0;
			instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
			instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
			instance.m_pIndexBuffer = pBuildIndexBuffer;
			instance.m_nVertexOffsetInBytes = 0;
			instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
			instance.m_nLightmapPageId = renderData.m_nLightmapPage;
			instance.m_bColorBufferHasIndirectLightingOnly = false;

			nIndexOffset += batch.indexCount;
		}

		indexBuilder.End( );
		pRenderContext->DrawInstances( group.m_nCount, pInstance );
	}

	return bHasPaintedSurfaces;
}


void CBrushBatchRender::DrawPaintForBatches( IMatRenderContext* pRenderContext, int nCount, const BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
	MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );

	pRenderContext->SetRenderingPaint( true );
	PIXEVENT( pRenderContext, "Paint" );

	for ( int i = 0; i < nCount; i++ )
	{
		const BrushInstanceGroup_t &group = pInstanceGroup[i];
		if ( !group.m_nHasPaintedSurfaces )
			continue;

		pRenderContext->Bind( group.m_pMaterial, NULL );

		// Only writing indices, we're potentially allocating too many, but that's ok.
		// Unused ones will be freed up
		// FIXME: Can we make this a static index buffer?
		IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
		CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
		indexBuilder.Lock( group.m_nIndexCount, 0 );
		int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);
		int nGroupCount = 0;
		for ( int j = 0; j < group.m_nCount; ++j )
		{
			const BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
			if ( !renderData.m_nHasPaintedSurfaces )
				continue;

			const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
			const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
			SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
			int nBatchIndexCount = 0;
			for ( int k = 0; k < batch.surfaceCount; k++ )
			{
				const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
				SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;
				if ( ( MSurf_Flags( surfID ) & SURFDRAW_PAINTED ) == 0 )
					continue;

				int nTriCount = BuildIndicesForSurface( indexBuilder, surfID );
				nBatchIndexCount += nTriCount * 3;
			}

			MeshInstanceData_t &instance = pInstance[ nGroupCount ];
			instance.m_pEnvCubemap = NULL;
			instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
			instance.m_pLightingState = NULL;
			instance.m_nBoneCount = 1;
			instance.m_pBoneRemap = NULL;
			instance.m_nIndexOffset = nIndexOffset;
			instance.m_nIndexCount = nBatchIndexCount;
			instance.m_nPrimType = MATERIAL_TRIANGLES;
			instance.m_pColorBuffer = NULL;
			instance.m_nColorVertexOffsetInBytes = 0;
			instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
			instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
			instance.m_pIndexBuffer = pBuildIndexBuffer;
			instance.m_nVertexOffsetInBytes = 0;
			instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
			instance.m_nLightmapPageId = renderData.m_nLightmapPage;
			instance.m_bColorBufferHasIndirectLightingOnly = false;

			nIndexOffset += nBatchIndexCount;
			++nGroupCount;
		}

		indexBuilder.End( );
		pRenderContext->DrawInstances( nGroupCount, pInstance );
	}

	pRenderContext->SetRenderingPaint( false );
}


// Draw decals
void CBrushBatchRender::DrawDecalsForBatches( IMatRenderContext *pRenderContext, 
	int nCount, const BrushArrayInstanceData_t *pInstanceData, brushrender_t **ppBrushRender )
{
	// FIXME: This could be better optimized by rendering across instances
	// but that's a deeper change: we'd need to have per-instance transforms
	// for each decal + shadow
	for ( int i = 0; i < nCount; ++i )
	{
		// Clear out the render list of decals
		DecalSurfacesInit( true );

		// Clear out the render lists of shadows
		g_pShadowMgr->ClearShadowRenderList( );

		const BrushArrayInstanceData_t &instance = pInstanceData[i];
		const brushrender_t *pRender = ppBrushRender[i];
		if ( !pRender )
			continue;

		SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( instance.m_pBrushModel->brush.firstmodelsurface, instance.m_pBrushModel->brush.pShared );

		bool bEncounteredDecals = false;
		for ( int s = 0; s < pRender->surfaceCount; ++s )
		{
			const brushrendersurface_t &surface = pRender->pSurfaces[s];
			SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;

			if ( SurfaceHasDecals( surfID ) )
			{
				bEncounteredDecals = true;
				DecalSurfaceAdd( surfID, BRUSHMODEL_DECAL_SORT_GROUP );
			}

			// Add overlay fragments to list.
			// FIXME: A little code support is necessary to get overlays working on brush models
			//	OverlayMgr()->AddFragmentListToRenderList( MSurf_OverlayFragmentList( surfID ), false );

			// Add render-to-texture shadows too....
			ShadowDecalHandle_t decalHandle = MSurf_ShadowDecals( surfID );
			if ( decalHandle != SHADOW_DECAL_HANDLE_INVALID )
			{
				bEncounteredDecals = true;
				g_pShadowMgr->AddShadowsOnSurfaceToRenderList( decalHandle );
			}
		}

		if ( bEncounteredDecals )
		{
			CBrushModelTransform pushTransform( *instance.m_pBrushToWorld, pRenderContext );

			// Draw all shadows on the brush
			g_pShadowMgr->RenderProjectedTextures( pRenderContext );

			DecalSurfaceDraw( pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP, instance.m_DiffuseModulation.w );

			// draw the flashlight lighting for the decals on the brush.
			g_pShadowMgr->DrawFlashlightDecals( pRenderContext, BRUSHMODEL_DECAL_SORT_GROUP, false, instance.m_DiffuseModulation.w );

			// Retire decals on opaque brushmodel surfaces
			R_DecalFlushDestroyList();

			
		}
	}
}


void CBrushBatchRender::DrawArrayDebugInformation( IMatRenderContext *pRenderContext, int nCount, const BrushBatchRenderData_t *pRenderData )
{
	if ( !g_ShaderDebug.anydebug )
		return;

	const Vector &vecViewOrigin = g_EngineRenderer->ViewOrigin();

	for ( int r = 0; r < nCount; ++r )
	{
		const BrushBatchRenderData_t &renderData = pRenderData[r];
		const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
		const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
		SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );

		Vector vecModelSpaceViewOrigin;
		VectorITransform( vecViewOrigin, *renderData.m_pInstanceData->m_pBrushToWorld, vecModelSpaceViewOrigin ); 

		CUtlVectorFixedGrowable< msurface2_t *, 512 > surfaceList;
		for ( int k = 0; k < batch.surfaceCount; k++ )
		{
			brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
			SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;

			if ( MSurf_Flags(surfID) & SURFDRAW_TRANS )
				continue;

			if ( ( MSurf_Flags( surfID ) & SURFDRAW_NOCULL) == 0 )
			{
				// Do a full backface cull here; we're not culling elsewhere in the pipeline
				// Yes, it's expensive, but this is a debug mode, so who cares?
				cplane_t *pSurfacePlane = renderData.m_pBrushRender->pPlanes[surface.planeIndex];
				float flDot = DotProduct( vecModelSpaceViewOrigin, pSurfacePlane->normal ) - pSurfacePlane->dist;
				bool bIsBackfacing = ( flDot < BACKFACE_EPSILON ) ? true : false;
				if ( bIsBackfacing != false )
					continue;
			}

			surfaceList.AddToTail( surfID );
		}

		// now draw debug for each drawn surface
		DrawDebugInformation( pRenderContext, *renderData.m_pInstanceData->m_pBrushToWorld, surfaceList.Base(), surfaceList.Count() );
	}
}


//-----------------------------------------------------------------------------
// Main entry point for rendering an array of brush models
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawBrushModelArray( IMatRenderContext* pRenderContext, int nCount, 
	const BrushArrayInstanceData_t *pInstanceData )
{
	brushrender_t **ppBrushRender = (brushrender_t**)stackalloc( nCount * sizeof(brushrender_t*) );
	CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > batchesToRender;
	BuildBatchListToDraw( nCount, pInstanceData, batchesToRender, ppBrushRender );

	int nBatchCount = batchesToRender.Count();
	BrushBatchRenderData_t *pBatchData = batchesToRender.Base();

	ComputeLightmapPages( nBatchCount, pBatchData );

	std::make_heap( pBatchData, pBatchData + nBatchCount, BatchSortLessFunc ); 
	std::sort_heap( pBatchData, pBatchData + nBatchCount, BatchSortLessFunc );

	CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > instanceGroups;
	int nMaxInstanceCount = ComputeInstanceGroups( pRenderContext, nBatchCount, pBatchData, instanceGroups );

	bool bHasPaintedSurfaces = DrawSortedBatchList( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
	if ( bHasPaintedSurfaces )
	{
		DrawPaintForBatches( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
	}

	DrawDecalsForBatches( pRenderContext, nCount, pInstanceData, ppBrushRender );

	DrawArrayDebugInformation( pRenderContext, nBatchCount, pBatchData );
}


//-----------------------------------------------------------------------------
// Builds the lists of shadow batches to draw
//-----------------------------------------------------------------------------
void CBrushBatchRender::BuildShadowBatchListToDraw( int nCount, const BrushArrayInstanceData_t *pInstanceData, 
	CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > &batchesToRender, int  nModelTypeFlags )
{
	for ( int i = 0; i < nCount; ++i )
	{
		const BrushArrayInstanceData_t &instance = pInstanceData[i];
		brushrender_t *pRender = g_BrushBatchRenderer.FindOrCreateRenderBatch( const_cast< model_t* >( instance.m_pBrushModel ) );
		if ( !pRender )
			continue;

		for ( int m = 0; m < pRender->meshCount; ++m )
		{
			brushrendermesh_t &mesh = pRender->pMeshes[m];
			int nBatchIndex = mesh.firstBatch;
			for ( int j = 0; j < mesh.batchCount; ++j, ++nBatchIndex )
			{
				// Select proper override material
				const brushrenderbatch_t &renderBatch = pRender->pBatches[ nBatchIndex ];
				int nAlphaTest = (int)renderBatch.pMaterial->IsAlphaTested();
				int nNoCull = (int)renderBatch.pMaterial->IsTwoSided();

				IMaterial *pDepthWriteMaterial;

				if ( nModelTypeFlags & STUDIO_SSAODEPTHTEXTURE )
				{
					pDepthWriteMaterial = g_pMaterialSSAODepthWrite[ nAlphaTest ][ nNoCull ];
				}
				else
				{
					pDepthWriteMaterial = g_pMaterialDepthWrite[ nAlphaTest ][ nNoCull ];
				}

				int nIndex = batchesToRender.AddToTail();
				BrushBatchRenderData_t &batch = batchesToRender[nIndex];
				batch.m_pInstanceData = &instance;
				batch.m_pBrushRender = pRender;
				batch.m_nBatchIndex = nBatchIndex;
				batch.m_nIsAlphaTested = nAlphaTest;
				batch.m_pMaterial = pDepthWriteMaterial;
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Sorts shadows
//-----------------------------------------------------------------------------
inline bool __cdecl CBrushBatchRender::ShadowSortLessFunc( const BrushBatchRenderData_t &left, const BrushBatchRenderData_t &right )
{
	if ( left.m_pMaterial != right.m_pMaterial )
		return left.m_pMaterial < right.m_pMaterial;
	if ( left.m_pInstanceData->m_pBrushToWorld != right.m_pInstanceData->m_pBrushToWorld )
		return left.m_pInstanceData->m_pBrushToWorld < right.m_pInstanceData->m_pBrushToWorld;
	return false;
}



//-----------------------------------------------------------------------------
// Draws an opaque (parts of a) brush model
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawShadowBatchList( IMatRenderContext* pRenderContext, int nCount, BrushInstanceGroup_t *pInstanceGroup, int nMaxInstanceCount )
{
	VPROF( "DrawShadowBatchList" );
	PIXEVENT( pRenderContext, "DrawShadowBatchList()" );

	pRenderContext->BindLightmapPage( MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP );

	MeshInstanceData_t *pInstance = (MeshInstanceData_t*)stackalloc( nMaxInstanceCount * sizeof(MeshInstanceData_t) );

	for ( int i = 0; i < nCount; i++ )
	{
		BrushInstanceGroup_t &group = pInstanceGroup[i];

		if ( group.m_pRenderData->m_nIsAlphaTested )
		{
			static unsigned int originalTextureVarCache = 0;
			IMaterialVar *pOriginalTextureVar = group.m_pActualMaterial->FindVarFast( "$basetexture", &originalTextureVarCache );
			static unsigned int originalTextureFrameVarCache = 0;
			IMaterialVar *pOriginalTextureFrameVar = group.m_pActualMaterial->FindVarFast( "$frame", &originalTextureFrameVarCache );
			static unsigned int originalAlphaRefCache = 0;
			IMaterialVar *pOriginalAlphaRefVar = group.m_pActualMaterial->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache );

			static unsigned int textureVarCache = 0;
			IMaterialVar *pTextureVar = group.m_pMaterial->FindVarFast( "$basetexture", &textureVarCache );
			static unsigned int textureFrameVarCache = 0;
			IMaterialVar *pTextureFrameVar = group.m_pMaterial->FindVarFast( "$frame", &textureFrameVarCache );
			static unsigned int alphaRefCache = 0;
			IMaterialVar *pAlphaRefVar = group.m_pMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache );

			if( pTextureVar && pOriginalTextureVar )
			{
				pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() );
			}

			if( pTextureFrameVar && pOriginalTextureFrameVar )
			{
				pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() );
			}

			if( pAlphaRefVar && pOriginalAlphaRefVar )
			{
				pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() );
			}
		}

		pRenderContext->Bind( group.m_pMaterial, NULL );

		// Only writing indices
		// FIXME: Can we make this a static index buffer?
		IIndexBuffer *pBuildIndexBuffer = pRenderContext->GetDynamicIndexBuffer();
		CIndexBuilder indexBuilder( pBuildIndexBuffer, MATERIAL_INDEX_FORMAT_16BIT );
		indexBuilder.Lock( group.m_nIndexCount, 0 );
		int nIndexOffset = indexBuilder.Offset() / sizeof(uint16);

		for ( int j = 0; j < group.m_nCount; ++j )
		{
			BrushBatchRenderData_t &renderData = group.m_pRenderData[j];
			const brushrenderbatch_t &batch = renderData.m_pBrushRender->pBatches[ renderData.m_nBatchIndex ];
			const model_t *pModel = renderData.m_pInstanceData->m_pBrushModel;
			SurfaceHandle_t firstSurfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface, pModel->brush.pShared );
			for ( int k = 0; k < batch.surfaceCount; k++ )
			{
				const brushrendersurface_t &surface = renderData.m_pBrushRender->pSurfaces[batch.firstSurface + k];
				SurfaceHandle_t surfID = firstSurfID + surface.surfaceIndex;

				Assert( !(MSurf_Flags( surfID ) & SURFDRAW_NODRAW) );
				BuildIndicesForSurface( indexBuilder, surfID );
			}

			MeshInstanceData_t &instance = pInstance[ j ];
			instance.m_pEnvCubemap = NULL;
			instance.m_pPoseToWorld = renderData.m_pInstanceData->m_pBrushToWorld;
			instance.m_pLightingState = NULL;
			instance.m_nBoneCount = 1;
			instance.m_pBoneRemap = NULL;
			instance.m_nIndexOffset = nIndexOffset;
			instance.m_nIndexCount = batch.indexCount;
			instance.m_nPrimType = MATERIAL_TRIANGLES;
			instance.m_pColorBuffer = NULL;
			instance.m_nColorVertexOffsetInBytes = 0;
			instance.m_pStencilState = renderData.m_pInstanceData->m_pStencilState;
			instance.m_pVertexBuffer = g_WorldStaticMeshes[ batch.sortID ];
			instance.m_pIndexBuffer = pBuildIndexBuffer;
			instance.m_nVertexOffsetInBytes = 0;
			instance.m_DiffuseModulation.Init( 1.0f, 1.0f, 1.0f, 1.0f );
			instance.m_nLightmapPageId = MATERIAL_SYSTEM_LIGHTMAP_PAGE_WHITE_BUMP;
			instance.m_bColorBufferHasIndirectLightingOnly = false;

			nIndexOffset += batch.indexCount;
		}

		indexBuilder.End( );
		pRenderContext->DrawInstances( group.m_nCount, pInstance );
	}
}


//-----------------------------------------------------------------------------
// Main entry point for rendering an array of brush model shadows
//-----------------------------------------------------------------------------
void CBrushBatchRender::DrawBrushModelShadowArray( IMatRenderContext* pRenderContext, int nCount, 
	const BrushArrayInstanceData_t *pInstanceData, int nModelTypeFlags )
{
	CUtlVectorFixedGrowable< BrushBatchRenderData_t, 1024 > batchesToRender;
	BuildShadowBatchListToDraw( nCount, pInstanceData, batchesToRender, nModelTypeFlags );

	int nBatchCount = batchesToRender.Count();
	BrushBatchRenderData_t *pBatchData = batchesToRender.Base();

	std::make_heap( pBatchData, pBatchData + nBatchCount, ShadowSortLessFunc ); 
	std::sort_heap( pBatchData, pBatchData + nBatchCount, ShadowSortLessFunc );

	CUtlVectorFixedGrowable< BrushInstanceGroup_t, 512 > instanceGroups;
	int nMaxInstanceCount = ComputeInstanceGroups( pRenderContext, nBatchCount, pBatchData, instanceGroups );

	DrawShadowBatchList( pRenderContext, instanceGroups.Count(), instanceGroups.Base(), nMaxInstanceCount );
}