/* Copyright (C) 2017 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * higher level interface on top of OpenGL to render basic objects:
 * terrain, models, sprites, particles etc.
 */

#include "precompiled.h"

#include <map>
#include <set>
#include <algorithm>

#include <boost/algorithm/string.hpp>

#include "Renderer.h"

#include "lib/bits.h"	// is_pow2
#include "lib/res/graphics/ogl_tex.h"
#include "lib/allocators/shared_ptr.h"
#include "maths/Matrix3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/World.h"
#include "ps/Loader.h"
#include "ps/ProfileViewer.h"
#include "graphics/Camera.h"
#include "graphics/Decal.h"
#include "graphics/FontManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/LOSTexture.h"
#include "graphics/MaterialManager.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ParticleManager.h"
#include "graphics/Patch.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "renderer/HWLightingModelRenderer.h"
#include "renderer/InstancingModelRenderer.h"
#include "renderer/ModelRenderer.h"
#include "renderer/OverlayRenderer.h"
#include "renderer/ParticleRenderer.h"
#include "renderer/PostprocManager.h"
#include "renderer/RenderModifiers.h"
#include "renderer/ShadowMap.h"
#include "renderer/SilhouetteRenderer.h"
#include "renderer/SkyManager.h"
#include "renderer/TerrainOverlay.h"
#include "renderer/TerrainRenderer.h"
#include "renderer/TimeManager.h"
#include "renderer/VertexBufferManager.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"

struct SScreenRect
{
	GLint x1, y1, x2, y2;
};

///////////////////////////////////////////////////////////////////////////////////
// CRendererStatsTable - Profile display of rendering stats

/**
 * Class CRendererStatsTable: Implementation of AbstractProfileTable to
 * display the renderer stats in-game.
 *
 * Accesses CRenderer::m_Stats by keeping the reference passed to the
 * constructor.
 */
class CRendererStatsTable : public AbstractProfileTable
{
	NONCOPYABLE(CRendererStatsTable);
public:
	CRendererStatsTable(const CRenderer::Stats& st);

	// Implementation of AbstractProfileTable interface
	CStr GetName();
	CStr GetTitle();
	size_t GetNumberRows();
	const std::vector<ProfileColumn>& GetColumns();
	CStr GetCellText(size_t row, size_t col);
	AbstractProfileTable* GetChild(size_t row);

private:
	/// Reference to the renderer singleton's stats
	const CRenderer::Stats& Stats;

	/// Column descriptions
	std::vector<ProfileColumn> columnDescriptions;

	enum {
		Row_DrawCalls = 0,
		Row_TerrainTris,
		Row_WaterTris,
		Row_ModelTris,
		Row_OverlayTris,
		Row_BlendSplats,
		Row_Particles,
		Row_VBReserved,
		Row_VBAllocated,
		Row_TextureMemory,
		Row_ShadersLoaded,

		// Must be last to count number of rows
		NumberRows
	};
};

// Construction
CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
	: Stats(st)
{
	columnDescriptions.push_back(ProfileColumn("Name", 230));
	columnDescriptions.push_back(ProfileColumn("Value", 100));
}

// Implementation of AbstractProfileTable interface
CStr CRendererStatsTable::GetName()
{
	return "renderer";
}

CStr CRendererStatsTable::GetTitle()
{
	return "Renderer statistics";
}

size_t CRendererStatsTable::GetNumberRows()
{
	return NumberRows;
}

const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
{
	return columnDescriptions;
}

CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
{
	char buf[256];

	switch(row)
	{
	case Row_DrawCalls:
		if (col == 0)
			return "# draw calls";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
		return buf;

	case Row_TerrainTris:
		if (col == 0)
			return "# terrain tris";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
		return buf;

	case Row_WaterTris:
		if (col == 0)
			return "# water tris";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
		return buf;

	case Row_ModelTris:
		if (col == 0)
			return "# model tris";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
		return buf;

	case Row_OverlayTris:
		if (col == 0)
			return "# overlay tris";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
		return buf;

	case Row_BlendSplats:
		if (col == 0)
			return "# blend splats";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
		return buf;

	case Row_Particles:
		if (col == 0)
			return "# particles";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
		return buf;

	case Row_VBReserved:
		if (col == 0)
			return "VB reserved";
		sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
		return buf;

	case Row_VBAllocated:
		if (col == 0)
			return "VB allocated";
		sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
		return buf;

	case Row_TextureMemory:
		if (col == 0)
			return "textures uploaded";
		sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
		return buf;

	case Row_ShadersLoaded:
		if (col == 0)
			return "shader effects loaded";
		sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
		return buf;

	default:
		return "???";
	}
}

AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
{
	return 0;
}


///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation

/**
 * Struct CRendererInternals: Truly hide data that is supposed to be hidden
 * in this structure so it won't even appear in header files.
 */
struct CRendererInternals
{
	NONCOPYABLE(CRendererInternals);
public:
	/// true if CRenderer::Open has been called
	bool IsOpen;

	/// true if shaders need to be reloaded
	bool ShadersDirty;

	/// Table to display renderer stats in-game via profile system
	CRendererStatsTable profileTable;

	/// Shader manager
	CShaderManager shaderManager;

	/// Water manager
	WaterManager waterManager;

	/// Sky manager
	SkyManager skyManager;

	/// Texture manager
	CTextureManager textureManager;

	/// Terrain renderer
	TerrainRenderer terrainRenderer;

	/// Overlay renderer
	OverlayRenderer overlayRenderer;

	/// Particle manager
	CParticleManager particleManager;

	/// Particle renderer
	ParticleRenderer particleRenderer;

	/// Material manager
	CMaterialManager materialManager;

	/// Time manager
	CTimeManager timeManager;

	/// Shadow map
	ShadowMap shadow;

	/// Postprocessing effect manager
	CPostprocManager postprocManager;

	CFontManager fontManager;

	SilhouetteRenderer silhouetteRenderer;

	/// Various model renderers
	struct Models
	{
		// NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer,
		// RenderModifier, etc) is mostly a relic of an older design that implemented
		// the different materials and rendering modes through extensive subclassing
		// and hooking objects together in various combinations.
		// The new design uses the CShaderManager API to abstract away the details
		// of rendering, and uses a data-driven approach to materials, so there are
		// now a small number of generic subclasses instead of many specialised subclasses,
		// but most of the old infrastructure hasn't been refactored out yet and leads to
		// some unwanted complexity.

		// Submitted models are split on two axes:
		//  - Normal vs Transp[arent] - alpha-blended models are stored in a separate
		//    list so we can draw them above/below the alpha-blended water plane correctly
		//  - Skinned vs Unskinned - with hardware lighting we don't need to
		//    duplicate mesh data per model instance (except for skinned models),
		//    so non-skinned models get different ModelVertexRenderers

		ModelRendererPtr NormalSkinned;
		ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
		ModelRendererPtr TranspSkinned;
		ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported

		ModelVertexRendererPtr VertexRendererShader;
		ModelVertexRendererPtr VertexInstancingShader;
		ModelVertexRendererPtr VertexGPUSkinningShader;

		LitRenderModifierPtr ModShader;
	} Model;

	CShaderDefines globalContext;

	CRendererInternals() :
		IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false)
	{
	}

	/**
	 * Load the OpenGL projection and modelview matrices and the viewport according
	 * to the given camera.
	 */
	void SetOpenGLCamera(const CCamera& camera)
	{
		CMatrix3D view;
		camera.m_Orientation.GetInverse(view);
		const CMatrix3D& proj = camera.GetProjection();

#if CONFIG2_GLES
#warning TODO: fix CRenderer camera handling for GLES (do not use global matrixes)
#else
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(&proj._11);

		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf(&view._11);
#endif

		g_Renderer.SetViewport(camera.GetViewPort());
	}

	/**
	 * Renders all non-alpha-blended models with the given context.
	 */
	void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags)
	{
		CShaderDefines contextSkinned = context;
		if (g_Renderer.m_Options.m_GPUSkinning)
		{
			contextSkinned.Add(str_USE_INSTANCING, str_1);
			contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
		}
		Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags);

		if (Model.NormalUnskinned != Model.NormalSkinned)
		{
			CShaderDefines contextUnskinned = context;
			contextUnskinned.Add(str_USE_INSTANCING, str_1);
			Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags);
		}
	}

	/**
	 * Renders all alpha-blended models with the given context.
	 */
	void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags)
	{
		CShaderDefines contextSkinned = context;
		if (g_Renderer.m_Options.m_GPUSkinning)
		{
			contextSkinned.Add(str_USE_INSTANCING, str_1);
			contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
		}
		Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags);

		if (Model.TranspUnskinned != Model.TranspSkinned)
		{
			CShaderDefines contextUnskinned = context;
			contextUnskinned.Add(str_USE_INSTANCING, str_1);
			Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags);
		}
	}
};

///////////////////////////////////////////////////////////////////////////////////
// CRenderer constructor
CRenderer::CRenderer()
{
	m = new CRendererInternals;
	m_WaterManager = &m->waterManager;
	m_SkyManager = &m->skyManager;

	g_ProfileViewer.AddRootTable(&m->profileTable);

	m_Width = 0;
	m_Height = 0;
	m_TerrainRenderMode = SOLID;
	m_WaterRenderMode = SOLID;
	m_ModelRenderMode = SOLID;
	m_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0;

	m_DisplayTerrainPriorities = false;
	m_SkipSubmit = false;

	m_Options.m_NoVBO = false;
	m_Options.m_RenderPath = RP_DEFAULT;
	m_Options.m_Shadows = false;
	m_Options.m_WaterEffects = false;
	m_Options.m_WaterFancyEffects = false;
	m_Options.m_WaterRealDepth = false;
	m_Options.m_WaterRefraction = false;
	m_Options.m_WaterReflection = false;
	m_Options.m_WaterShadows = false;
	m_Options.m_ShadowAlphaFix = true;
	m_Options.m_ARBProgramShadow = true;
	m_Options.m_ShadowPCF = false;
	m_Options.m_Particles = false;
	m_Options.m_Silhouettes = false;
	m_Options.m_PreferGLSL = false;
	m_Options.m_Fog = false;
	m_Options.m_ForceAlphaTest = false;
	m_Options.m_GPUSkinning = false;
	m_Options.m_SmoothLOS = false;
	m_Options.m_Postproc = false;
	m_Options.m_ShowSky = false;
	m_Options.m_DisplayFrustum = false;

	// TODO: be more consistent in use of the config system
	CFG_GET_VAL("preferglsl", m_Options.m_PreferGLSL);
	CFG_GET_VAL("forcealphatest", m_Options.m_ForceAlphaTest);
	CFG_GET_VAL("gpuskinning", m_Options.m_GPUSkinning);
	CFG_GET_VAL("smoothlos", m_Options.m_SmoothLOS);
	CFG_GET_VAL("postproc", m_Options.m_Postproc);

	CStr skystring = "0 0 0";
	CColor skycolor;
	CFG_GET_VAL("skycolor", skystring);
	if (skycolor.ParseString(skystring, 255.f))
		SetClearColor(skycolor.AsSColor4ub());

#if CONFIG2_GLES
	// Override config option since GLES only supports GLSL
	m_Options.m_PreferGLSL = true;
#endif

	m_ShadowZBias = 0.02f;
	m_ShadowMapSize = 0;

	m_LightEnv = NULL;

	m_CurrentScene = NULL;

	m_hCompositeAlphaMap = 0;

	m_Stats.Reset();

	RegisterFileReloadFunc(ReloadChangedFileCB, this);
}

///////////////////////////////////////////////////////////////////////////////////
// CRenderer destructor
CRenderer::~CRenderer()
{
	UnregisterFileReloadFunc(ReloadChangedFileCB, this);

	// we no longer UnloadAlphaMaps / UnloadWaterTextures here -
	// that is the responsibility of the module that asked for
	// them to be loaded (i.e. CGameView).
	delete m;
}


///////////////////////////////////////////////////////////////////////////////////
// EnumCaps: build card cap bits
void CRenderer::EnumCaps()
{
	// assume support for nothing
	m_Caps.m_VBO = false;
	m_Caps.m_ARBProgram = false;
	m_Caps.m_ARBProgramShadow = false;
	m_Caps.m_VertexShader = false;
	m_Caps.m_FragmentShader = false;
	m_Caps.m_Shadows = false;
	m_Caps.m_PrettyWater = false;

	// now start querying extensions
	if (!m_Options.m_NoVBO && ogl_HaveExtension("GL_ARB_vertex_buffer_object"))
		m_Caps.m_VBO = true;

	if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL))
	{
		m_Caps.m_ARBProgram = true;
		if (ogl_HaveExtension("GL_ARB_fragment_program_shadow"))
			m_Caps.m_ARBProgramShadow = true;
	}

	if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL))
	{
		if (ogl_HaveExtension("GL_ARB_vertex_shader"))
			m_Caps.m_VertexShader = true;
		if (ogl_HaveExtension("GL_ARB_fragment_shader"))
			m_Caps.m_FragmentShader = true;
	}

#if CONFIG2_GLES
	m_Caps.m_Shadows = true;
#else
	if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL))
	{
		if (ogl_max_tex_units >= 4)
			m_Caps.m_Shadows = true;
	}
#endif

#if CONFIG2_GLES
	m_Caps.m_PrettyWater = true;
#else
	if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", "GL_EXT_framebuffer_object", NULL))
		m_Caps.m_PrettyWater = true;
#endif
}

void CRenderer::RecomputeSystemShaderDefines()
{
	CShaderDefines defines;

	if (GetRenderPath() == RP_SHADER && m_Caps.m_ARBProgram)
		defines.Add(str_SYS_HAS_ARB, str_1);

	if (GetRenderPath() == RP_SHADER && m_Caps.m_VertexShader && m_Caps.m_FragmentShader)
		defines.Add(str_SYS_HAS_GLSL, str_1);

	if (m_Options.m_PreferGLSL)
		defines.Add(str_SYS_PREFER_GLSL, str_1);

	m_SystemShaderDefines = defines;
}

void CRenderer::ReloadShaders()
{
	ENSURE(m->IsOpen);

	m->globalContext = m_SystemShaderDefines;

	if (m_Caps.m_Shadows && m_Options.m_Shadows)
	{
		m->globalContext.Add(str_USE_SHADOW, str_1);
		if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow)
			m->globalContext.Add(str_USE_FP_SHADOW, str_1);
		if (m_Options.m_ShadowPCF)
			m->globalContext.Add(str_USE_SHADOW_PCF, str_1);
#if !CONFIG2_GLES
		m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1);
#endif
	}

	if (m_LightEnv)
		m->globalContext.Add(CStrIntern("LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()), str_1);

	if (m_Options.m_PreferGLSL && m_Options.m_Fog)
		m->globalContext.Add(str_USE_FOG, str_1);

	m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier());

	bool cpuLighting = (GetRenderPath() == RP_FIXED);
	m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting));
	m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, m_Options.m_PreferGLSL));

	if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too
	{
		m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, m_Options.m_PreferGLSL));
		m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
		m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
	}
	else
	{
		m->Model.VertexGPUSkinningShader.reset();
		m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
		m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
	}

	// Use instancing renderers in shader mode
	if (GetRenderPath() == RP_SHADER)
	{
		m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
		m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
	}
	else
	{
		m->Model.NormalUnskinned = m->Model.NormalSkinned;
		m->Model.TranspUnskinned = m->Model.TranspSkinned;
	}

	m->ShadersDirty = false;
}

bool CRenderer::Open(int width, int height)
{
	m->IsOpen = true;

	// Must query card capabilities before creating renderers that depend
	// on card capabilities.
	EnumCaps();

	// Dimensions
	m_Width = width;
	m_Height = height;

	// set packing parameters
	glPixelStorei(GL_PACK_ALIGNMENT,1);
	glPixelStorei(GL_UNPACK_ALIGNMENT,1);

	// setup default state
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);
	glEnable(GL_CULL_FACE);

	GLint bits;
	glGetIntegerv(GL_DEPTH_BITS,&bits);
	LOGMESSAGE("CRenderer::Open: depth bits %d",bits);
	glGetIntegerv(GL_STENCIL_BITS,&bits);
	LOGMESSAGE("CRenderer::Open: stencil bits %d",bits);
	glGetIntegerv(GL_ALPHA_BITS,&bits);
	LOGMESSAGE("CRenderer::Open: alpha bits %d",bits);

	// Validate the currently selected render path
	SetRenderPath(m_Options.m_RenderPath);

	RecomputeSystemShaderDefines();

	// Let component renderers perform one-time initialization after graphics capabilities and
	// the shader path have been determined.
	m->overlayRenderer.Initialize();

	if (m_Options.m_Postproc)
		m->postprocManager.Initialize();

	return true;
}

// resize renderer view
void CRenderer::Resize(int width, int height)
{
	// need to recreate the shadow map object to resize the shadow texture
	m->shadow.RecreateTexture();

	m_Width = width;
	m_Height = height;

	m->postprocManager.Resize();

	m_WaterManager->Resize();
}

//////////////////////////////////////////////////////////////////////////////////////////
// SetOptionBool: set boolean renderer option
void CRenderer::SetOptionBool(enum Option opt,bool value)
{
	// Don't do anything if the option didn't change from its previous value.
	if (value == GetOptionBool(opt))
		return;

	switch (opt) {
		case OPT_NOVBO:
			m_Options.m_NoVBO = value;
			break;
		case OPT_SHADOWS:
			m_Options.m_Shadows = value;
			MakeShadersDirty();
			break;
		case OPT_WATEREFFECTS:
			m_Options.m_WaterEffects = value;
			break;
		case OPT_WATERFANCYEFFECTS:
			m_Options.m_WaterFancyEffects = value;
			break;
		case OPT_WATERREALDEPTH:
			m_Options.m_WaterRealDepth = value;
			break;
		case OPT_WATERREFLECTION:
			m_Options.m_WaterReflection = value;
			break;
		case OPT_WATERREFRACTION:
			m_Options.m_WaterRefraction = value;
			break;
		case OPT_SHADOWSONWATER:
			m_Options.m_WaterShadows = value;
			break;
		case OPT_SHADOWPCF:
			m_Options.m_ShadowPCF = value;
			MakeShadersDirty();
			break;
		case OPT_PARTICLES:
			m_Options.m_Particles = value;
			break;
		case OPT_PREFERGLSL:
			m_Options.m_PreferGLSL = value;
			MakeShadersDirty();
			RecomputeSystemShaderDefines();
			break;
		case OPT_FOG:
			m_Options.m_Fog = value;
			MakeShadersDirty();
			break;
		case OPT_SILHOUETTES:
			m_Options.m_Silhouettes = value;
			break;
		case OPT_SHOWSKY:
			m_Options.m_ShowSky = value;
			break;
		case OPT_SMOOTHLOS:
			m_Options.m_SmoothLOS = value;
			break;
		case OPT_POSTPROC:
			m_Options.m_Postproc = value;
			break;
		case OPT_DISPLAYFRUSTUM:
			m_Options.m_DisplayFrustum = value;
			break;
		default:
			debug_warn(L"CRenderer::SetOptionBool: unknown option");
			break;
	}
}

//////////////////////////////////////////////////////////////////////////////////////////
// GetOptionBool: get boolean renderer option
bool CRenderer::GetOptionBool(enum Option opt) const
{
	switch (opt) {
		case OPT_NOVBO:
			return m_Options.m_NoVBO;
		case OPT_SHADOWS:
			return m_Options.m_Shadows;
		case OPT_WATEREFFECTS:
			return m_Options.m_WaterEffects;
		case OPT_WATERFANCYEFFECTS:
			return m_Options.m_WaterFancyEffects;
		case OPT_WATERREALDEPTH:
			return m_Options.m_WaterRealDepth;
		case OPT_WATERREFLECTION:
			return m_Options.m_WaterReflection;
		case OPT_WATERREFRACTION:
			return m_Options.m_WaterRefraction;
		case OPT_SHADOWSONWATER:
			return m_Options.m_WaterShadows;
		case OPT_SHADOWPCF:
			return m_Options.m_ShadowPCF;
		case OPT_PARTICLES:
			return m_Options.m_Particles;
		case OPT_PREFERGLSL:
			return m_Options.m_PreferGLSL;
		case OPT_FOG:
			return m_Options.m_Fog;
		case OPT_SILHOUETTES:
			return m_Options.m_Silhouettes;
		case OPT_SHOWSKY:
			return m_Options.m_ShowSky;
		case OPT_SMOOTHLOS:
			return m_Options.m_SmoothLOS;
		case OPT_POSTPROC:
			return m_Options.m_Postproc;
		case OPT_DISPLAYFRUSTUM:
			return m_Options.m_DisplayFrustum;
		default:
			debug_warn(L"CRenderer::GetOptionBool: unknown option");
			break;
	}

	return false;
}

//////////////////////////////////////////////////////////////////////////////////////////
// SetRenderPath: Select the preferred render path.
// This may only be called before Open(), because the layout of vertex arrays and other
// data may depend on the chosen render path.
void CRenderer::SetRenderPath(RenderPath rp)
{
	if (!m->IsOpen)
	{
		// Delay until Open() is called.
		m_Options.m_RenderPath = rp;
		return;
	}

	// Renderer has been opened, so validate the selected renderpath
	if (rp == RP_DEFAULT)
	{
		if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL))
			rp = RP_SHADER;
		else
			rp = RP_FIXED;
	}

	if (rp == RP_SHADER)
	{
		if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL)))
		{
			LOGWARNING("Falling back to fixed function\n");
			rp = RP_FIXED;
		}
	}

	m_Options.m_RenderPath = rp;

	MakeShadersDirty();
	RecomputeSystemShaderDefines();

	// We might need to regenerate some render data after changing path
	if (g_Game)
		g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR);
}


CStr CRenderer::GetRenderPathName(RenderPath rp)
{
	switch(rp) {
	case RP_DEFAULT: return "default";
	case RP_FIXED: return "fixed";
	case RP_SHADER: return "shader";
	default: return "(invalid)";
	}
}

CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name)
{
	if (name == "fixed")
		return RP_FIXED;
	if (name == "shader")
		return RP_SHADER;
	if (name == "default")
		return RP_DEFAULT;

	LOGWARNING("Unknown render path name '%s', assuming 'default'", name.c_str());
	return RP_DEFAULT;
}


//////////////////////////////////////////////////////////////////////////////////////////
// BeginFrame: signal frame start
void CRenderer::BeginFrame()
{
	PROFILE("begin frame");

	// zero out all the per-frame stats
	m_Stats.Reset();

	// choose model renderers for this frame

	if (m->ShadersDirty)
		ReloadShaders();

	m->Model.ModShader->SetShadowMap(&m->shadow);
	m->Model.ModShader->SetLightEnv(m_LightEnv);
}

//////////////////////////////////////////////////////////////////////////////////////////
void CRenderer::SetSimulation(CSimulation2* simulation)
{
	// set current simulation context for terrain renderer
	m->terrainRenderer.SetSimulation(simulation);
}

// SetClearColor: set color used to clear screen in BeginFrame()
void CRenderer::SetClearColor(SColor4ub color)
{
	m_ClearColor[0] = float(color.R) / 255.0f;
	m_ClearColor[1] = float(color.G) / 255.0f;
	m_ClearColor[2] = float(color.B) / 255.0f;
	m_ClearColor[3] = float(color.A) / 255.0f;
}

void CRenderer::RenderShadowMap(const CShaderDefines& context)
{
	PROFILE3_GPU("shadow map");

	m->shadow.BeginRender();

	{
		PROFILE("render patches");
		glCullFace(GL_FRONT);
		glEnable(GL_CULL_FACE);
		m->terrainRenderer.RenderPatches(CULL_SHADOWS);
		glCullFace(GL_BACK);
	}

	CShaderDefines contextCast = context;
	contextCast.Add(str_MODE_SHADOWCAST, str_1);

	{
		PROFILE("render models");
		m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS);
	}

	{
		PROFILE("render transparent models");
		// disable face-culling for two-sided models
		glDisable(GL_CULL_FACE);
		m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS);
		glEnable(GL_CULL_FACE);
	}

	m->shadow.EndRender();

	m->SetOpenGLCamera(m_ViewCamera);
}

void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup)
{
	PROFILE3_GPU("patches");

#if CONFIG2_GLES
#warning TODO: implement wireface/edged rendering mode GLES
#else
	// switch on wireframe if we need it
	if (m_TerrainRenderMode == WIREFRAME)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
#endif

	// render all the patches, including blend pass
	if (GetRenderPath() == RP_SHADER)
		m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0);
	else
		m->terrainRenderer.RenderTerrain(cullGroup);


#if !CONFIG2_GLES
	if (m_TerrainRenderMode == WIREFRAME)
	{
		// switch wireframe off again
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	else if (m_TerrainRenderMode == EDGED_FACES)
	{
		// edged faces: need to make a second pass over the data:
		// first switch on wireframe
		glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

		// setup some renderstate ..
		pglActiveTextureARB(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
		glColor3f(0.5f, 0.5f, 1.0f);
		glLineWidth(2.0f);

		// render tiles edges
		m->terrainRenderer.RenderPatches(cullGroup);

		// set color for outline
		glColor3f(0, 0, 1);
		glLineWidth(4.0f);

		// render outline of each patch
		m->terrainRenderer.RenderOutlines(cullGroup);

		// .. and restore the renderstates
		glLineWidth(1.0f);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
#endif
}

void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup)
{
	PROFILE3_GPU("models");

	int flags = 0;

#if !CONFIG2_GLES
	if (m_ModelRenderMode == WIREFRAME)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
#endif

	m->CallModelRenderers(context, cullGroup, flags);

#if !CONFIG2_GLES
	if (m_ModelRenderMode == WIREFRAME)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	else if (m_ModelRenderMode == EDGED_FACES)
	{
		CShaderDefines contextWireframe = context;
		contextWireframe.Add(str_MODE_WIREFRAME, str_1);

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glDisable(GL_TEXTURE_2D);

		m->CallModelRenderers(contextWireframe, cullGroup, flags);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
#endif
}

void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling)
{
	PROFILE3_GPU("transparent models");

	int flags = 0;

#if !CONFIG2_GLES
	// switch on wireframe if we need it
	if (m_ModelRenderMode == WIREFRAME)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
#endif

	// disable face culling for two-sided models in sub-renders
	if (disableFaceCulling)
		glDisable(GL_CULL_FACE);

	CShaderDefines contextOpaque = context;
	contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1);

	CShaderDefines contextBlend = context;
	contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1);

	if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE)
		m->CallTranspModelRenderers(contextOpaque, cullGroup, flags);

	if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND)
		m->CallTranspModelRenderers(contextBlend, cullGroup, flags);

	if (disableFaceCulling)
		glEnable(GL_CULL_FACE);

#if !CONFIG2_GLES
	if (m_ModelRenderMode == WIREFRAME)
	{
		// switch wireframe off again
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	else if (m_ModelRenderMode == EDGED_FACES)
	{
		CShaderDefines contextWireframe = contextOpaque;
		contextWireframe.Add(str_MODE_WIREFRAME, str_1);

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glDisable(GL_TEXTURE_2D);

		m->CallTranspModelRenderers(contextWireframe, cullGroup, flags);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
#endif
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
// Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html
// - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test)
void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const
{
	// First, we'll convert the given clip plane to camera space, then we'll
	// Get the view matrix and normal matrix (top 3x3 part of view matrix)
	CMatrix3D normalMatrix = camera.m_Orientation.GetTranspose();
	CVector4D camPlane = normalMatrix.Transform(worldPlane);

	CMatrix3D matrix = camera.GetProjection();

	// Calculate the clip-space corner point opposite the clipping plane
	// as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and
	// transform it into camera space by multiplying it
	// by the inverse of the projection matrix

	CVector4D q;
	q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0];
	q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5];
	q.Z = 1.0f/matrix[11];
	q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14];

	// Calculate the scaled plane vector
	CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q));

	// Replace the third row of the projection matrix
	matrix[2] = c.X;
	matrix[6] = c.Y;
	matrix[10] = c.Z - matrix[11];
	matrix[14] = c.W;

	// Load it back into the camera
	camera.SetProjection(matrix);
}

void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
	WaterManager& wm = m->waterManager;

	float fov = m_ViewCamera.GetFOV();

	// Expand fov slightly since ripples can reflect parts of the scene that
	// are slightly outside the normal camera view, and we want to avoid any
	// noticeable edge-filtering artifacts
	fov *= 1.05f;

	camera = m_ViewCamera;

	// Temporarily change the camera to one that is reflected.
	// Also, for texturing purposes, make it render to a view port the size of the
	// water texture, stretch the image according to our aspect ratio so it covers
	// the whole screen despite being rendered into a square, and cover slightly more
	// of the view so we can see wavy reflections of slightly off-screen objects.
	camera.m_Orientation.Scale(1, -1, 1);
	camera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0);
	camera.UpdateFrustum(scissor);
	camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight));

	SViewPort vp;
	vp.m_Height = wm.m_RefTextureSize;
	vp.m_Width = wm.m_RefTextureSize;
	vp.m_X = 0;
	vp.m_Y = 0;
	camera.SetViewPort(vp);
	camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov);
	CMatrix3D scaleMat;
	scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f);
	camera.m_ProjMat = scaleMat * camera.m_ProjMat;

	CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f);
	SetObliqueFrustumClipping(camera, camPlane);

}

void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
	WaterManager& wm = m->waterManager;

	float fov = m_ViewCamera.GetFOV();

	// Expand fov slightly since ripples can reflect parts of the scene that
	// are slightly outside the normal camera view, and we want to avoid any
	// noticeable edge-filtering artifacts
	fov *= 1.05f;

	camera = m_ViewCamera;

	// Temporarily change the camera to make it render to a view port the size of the
	// water texture, stretch the image according to our aspect ratio so it covers
	// the whole screen despite being rendered into a square, and cover slightly more
	// of the view so we can see wavy refractions of slightly off-screen objects.
	camera.UpdateFrustum(scissor);
	camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f));	// add some to avoid artifacts near steep shores.

	SViewPort vp;
	vp.m_Height = wm.m_RefTextureSize;
	vp.m_Width = wm.m_RefTextureSize;
	vp.m_X = 0;
	vp.m_Y = 0;
	camera.SetViewPort(vp);
	camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov);
	CMatrix3D scaleMat;
	scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f);
	camera.m_ProjMat = scaleMat * camera.m_ProjMat;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderReflections: render the water reflections to the reflection texture
void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor)
{
	PROFILE3_GPU("water reflections");

	// Save the post-processing framebuffer.
	GLint fbo;
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo);

	WaterManager& wm = m->waterManager;

	// Remember old camera
	CCamera normalCamera = m_ViewCamera;

	ComputeReflectionCamera(m_ViewCamera, scissor);

	m->SetOpenGLCamera(m_ViewCamera);

	// Save the model-view-projection matrix so the shaders can use it for projective texturing
	wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection();

	float vpHeight = wm.m_RefTextureSize;
	float vpWidth = wm.m_RefTextureSize;

	SScreenRect screenScissor;
	screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth);
	screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight);
	screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth);
	screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight);

	glEnable(GL_SCISSOR_TEST);
	glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1);

	// try binding the framebuffer
	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo);

	glClearColor(0.5f,0.5f,1.0f,0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glFrontFace(GL_CW);

	if (!m_Options.m_WaterReflection)
	{
		m->skyManager.RenderSky();
		ogl_WarnIfError();
	}
	else
	{
		// Render terrain and models
		RenderPatches(context, CULL_REFLECTIONS);
		ogl_WarnIfError();
		RenderModels(context, CULL_REFLECTIONS);
		ogl_WarnIfError();
		RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true);
		ogl_WarnIfError();
	}
	glFrontFace(GL_CCW);

	// Particles are always oriented to face the camera in the vertex shader,
	// so they don't need the inverted glFrontFace
	if (m_Options.m_Particles)
	{
		RenderParticles(CULL_REFLECTIONS);
		ogl_WarnIfError();
	}

	glDisable(GL_SCISSOR_TEST);

  	// Reset old camera
  	m_ViewCamera = normalCamera;
  	m->SetOpenGLCamera(m_ViewCamera);

	// rebind post-processing frambuffer.
	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

	return;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderRefractions: render the water refractions to the refraction texture
void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor)
{
	PROFILE3_GPU("water refractions");

	// Save the post-processing framebuffer.
	GLint fbo;
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo);

	WaterManager& wm = m->waterManager;

	// Remember old camera
	CCamera normalCamera = m_ViewCamera;

	ComputeRefractionCamera(m_ViewCamera, scissor);

	CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f);
	SetObliqueFrustumClipping(m_ViewCamera, camPlane);

	m->SetOpenGLCamera(m_ViewCamera);

	// Save the model-view-projection matrix so the shaders can use it for projective texturing
	wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection();

	float vpHeight = wm.m_RefTextureSize;
	float vpWidth = wm.m_RefTextureSize;

	SScreenRect screenScissor;
	screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth);
	screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight);
	screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth);
	screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight);

	glEnable(GL_SCISSOR_TEST);
	glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1);

	// try binding the framebuffer
	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo);

	glClearColor(1.0f,0.0f,0.0f,0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Render terrain and models
	RenderPatches(context, CULL_REFRACTIONS);
	ogl_WarnIfError();
	RenderModels(context, CULL_REFRACTIONS);
	ogl_WarnIfError();
	RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false);
	ogl_WarnIfError();

	glDisable(GL_SCISSOR_TEST);

  	// Reset old camera
  	m_ViewCamera = normalCamera;
  	m->SetOpenGLCamera(m_ViewCamera);

	// rebind post-processing frambuffer.
	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

	return;
}

void CRenderer::RenderSilhouettes(const CShaderDefines& context)
{
	PROFILE3_GPU("silhouettes");

	CShaderDefines contextOccluder = context;
	contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1);

	CShaderDefines contextDisplay = context;
	contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1);

	// Render silhouettes of units hidden behind terrain or occluders.
	// To avoid breaking the standard rendering of alpha-blended objects, this
	// has to be done in a separate pass.
	// First we render all occluders into depth, then render all units with
	// inverted depth test so any behind an occluder will get drawn in a constant
	// color.

	float silhouetteAlpha = 0.75f;

	// Silhouette blending requires an almost-universally-supported extension;
	// fall back to non-blended if unavailable
	if (!ogl_HaveExtension("GL_EXT_blend_color"))
		silhouetteAlpha = 1.f;

	glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glColorMask(0, 0, 0, 0);

	// Render occluders:

	{
		PROFILE("render patches");

		// To prevent units displaying silhouettes when parts of their model
		// protrude into the ground, only occlude with the back faces of the
		// terrain (so silhouettes will still display when behind hills)
		glCullFace(GL_FRONT);
		m->terrainRenderer.RenderPatches(CULL_SILHOUETTE_OCCLUDER);
		glCullFace(GL_BACK);
	}

	{
		PROFILE("render model occluders");
		m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
	}

	{
		PROFILE("render transparent occluders");
		m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
	}

	glDepthFunc(GL_GEQUAL);
	glColorMask(1, 1, 1, 1);

	// Render more efficiently if alpha == 1
	if (silhouetteAlpha == 1.f)
	{
		// Ideally we'd render objects back-to-front so nearer silhouettes would
		// appear on top, but sorting has non-zero cost. So we'll keep the depth
		// write enabled, to do the opposite - far objects will consistently appear
		// on top.
		glDepthMask(0);
	}
	else
	{
		// Since we can't sort, we'll use the stencil buffer to ensure we only draw
		// a pixel once (using the color of whatever model happens to be drawn first).
		glEnable(GL_BLEND);
		glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
		pglBlendColorEXT(0, 0, 0, silhouetteAlpha);

		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1);
		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	}

	{
		PROFILE("render model casters");
		m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0);
	}

	{
		PROFILE("render transparent casters");
		m->CallTranspModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0);
	}

	// Restore state
	glDepthFunc(GL_LEQUAL);
	if (silhouetteAlpha == 1.f)
	{
		glDepthMask(1);
	}
	else
	{
		glDisable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		pglBlendColorEXT(0, 0, 0, 0);
		glDisable(GL_STENCIL_TEST);
	}
}

void CRenderer::RenderParticles(int cullGroup)
{
	// Only supported in shader modes
	if (GetRenderPath() != RP_SHADER)
		return;

	PROFILE3_GPU("particles");

	m->particleRenderer.RenderParticles(cullGroup);

#if !CONFIG2_GLES
	if (m_ModelRenderMode == EDGED_FACES)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

		glDisable(GL_TEXTURE_2D);
		glColor3f(0.0f, 0.5f, 0.0f);

		m->particleRenderer.RenderParticles(true);

		CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
		shaderTech->BeginPass();
		CShaderProgramPtr shader = shaderTech->GetShader();
		shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f);
		shader->Uniform(str_transform, m_ViewCamera.GetViewProjection());

		m->particleRenderer.RenderBounds(cullGroup, shader);

		shaderTech->EndPass();

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderSubmissions: force rendering of any batched objects
void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor)
{
	PROFILE3("render submissions");

	GetScene().GetLOSTexture().InterpolateLOS();

	if (m_Options.m_Postproc)
	{
		m->postprocManager.Initialize();
		m->postprocManager.CaptureRenderOutput();
	}

	CShaderDefines context = m->globalContext;

	int cullGroup = CULL_DEFAULT;

	ogl_WarnIfError();

	// Set the camera
	m->SetOpenGLCamera(m_ViewCamera);

	// Prepare model renderers
	{
	PROFILE3("prepare models");
	m->Model.NormalSkinned->PrepareModels();
	m->Model.TranspSkinned->PrepareModels();
	if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
		m->Model.NormalUnskinned->PrepareModels();
	if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
		m->Model.TranspUnskinned->PrepareModels();
	}

	m->terrainRenderer.PrepareForRendering();

	m->overlayRenderer.PrepareForRendering();

	m->particleRenderer.PrepareForRendering(context);

	if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER)
	{
		RenderShadowMap(context);
	}

	{
		PROFILE3_GPU("clear buffers");
		glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	}

	ogl_WarnIfError();

	if (m_WaterManager->m_RenderWater)
	{
		if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater())
		{
			PROFILE3_GPU("water scissor");
			RenderReflections(context, waterScissor);

			if (m_Options.m_WaterRefraction)
				RenderRefractions(context, waterScissor);
		}
	}

	if (m_Options.m_ShowSky)
	{
		m->skyManager.RenderSky();
	}

	// render submitted patches and models
	RenderPatches(context, cullGroup);
	ogl_WarnIfError();

	// render debug-related terrain overlays
	ITerrainOverlay::RenderOverlaysBeforeWater();
	ogl_WarnIfError();

	// render other debug-related overlays before water (so they can be seen when underwater)
	m->overlayRenderer.RenderOverlaysBeforeWater();
	ogl_WarnIfError();

	RenderModels(context, cullGroup);
	ogl_WarnIfError();

	// render water
	if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0)
	{
		// render transparent stuff, but only the solid parts that can occlude block water
		RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false);
		ogl_WarnIfError();

		m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow);
		ogl_WarnIfError();

		// render transparent stuff again, but only the blended parts that overlap water
		RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false);
		ogl_WarnIfError();
	}
	else
	{
		// render transparent stuff, so it can overlap models/terrain
		RenderTransparentModels(context, cullGroup, TRANSPARENT, false);
		ogl_WarnIfError();
	}

	// render debug-related terrain overlays
	ITerrainOverlay::RenderOverlaysAfterWater(cullGroup);
	ogl_WarnIfError();

	// render some other overlays after water (so they can be displayed on top of water)
	m->overlayRenderer.RenderOverlaysAfterWater();
	ogl_WarnIfError();

	// particles are transparent so render after water
	if (m_Options.m_Particles)
	{
		RenderParticles(cullGroup);
		ogl_WarnIfError();
	}

	if (m_Options.m_Postproc)
	{
		m->postprocManager.ApplyPostproc();
		m->postprocManager.ReleaseRenderOutput();
	}

	if (m_Options.m_Silhouettes)
	{
		RenderSilhouettes(context);
	}

#if !CONFIG2_GLES
	// Clean up texture blend mode so particles and other things render OK
	// (really this should be cleaned up by whoever set it)
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#endif

	// render debug lines
	if (m_Options.m_DisplayFrustum)
	{
		DisplayFrustum();
		m->shadow.RenderDebugBounds();
		m->shadow.RenderDebugTexture();
		ogl_WarnIfError();
	}

	m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera);

	// render overlays that should appear on top of all other objects
	m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera);
	ogl_WarnIfError();

}

///////////////////////////////////////////////////////////////////////////////////////////////////
// EndFrame: signal frame end
void CRenderer::EndFrame()
{
	PROFILE3("end frame");

	// empty lists
	m->terrainRenderer.EndFrame();
	m->overlayRenderer.EndFrame();
	m->particleRenderer.EndFrame();
	m->silhouetteRenderer.EndFrame();

	// Finish model renderers
	m->Model.NormalSkinned->EndFrame();
	m->Model.TranspSkinned->EndFrame();
	if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
		m->Model.NormalUnskinned->EndFrame();
	if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
		m->Model.TranspUnskinned->EndFrame();

	ogl_tex_bind(0, 0);

	{
		PROFILE3("error check");
		int err = glGetError();
		if (err)
		{
			ONCE(LOGERROR("CRenderer::EndFrame: GL errors %s (%04x) occurred", ogl_GetErrorName(err), err));
		}
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// DisplayFrustum: debug displays
//  - white: cull camera frustum
//  - red: bounds of shadow casting objects
void CRenderer::DisplayFrustum()
{
#if CONFIG2_GLES
#warning TODO: implement CRenderer::DisplayFrustum for GLES
#else
	glDepthMask(0);
	glDisable(GL_CULL_FACE);
	glDisable(GL_TEXTURE_2D);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4ub(255,255,255,64);
	m_CullCamera.Render(2);
	glDisable(GL_BLEND);

	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor3ub(255,255,255);
	m_CullCamera.Render(2);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	glEnable(GL_CULL_FACE);
	glDepthMask(1);
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Text overlay rendering
void CRenderer::RenderTextOverlays()
{
	PROFILE3_GPU("text overlays");

	if (m_DisplayTerrainPriorities)
		m->terrainRenderer.RenderPriorities(CULL_DEFAULT);

	ogl_WarnIfError();
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// SetSceneCamera: setup projection and transform of camera and adjust viewport to current view
// The camera always represents the actual camera used to render a scene, not any virtual camera
// used for shadow rendering or reflections.
void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera)
{
	m_ViewCamera = viewCamera;
	m_CullCamera = cullCamera;

	if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER)
		m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir());
}


void CRenderer::SetViewport(const SViewPort &vp)
{
	m_Viewport = vp;
	glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height);
}

SViewPort CRenderer::GetViewport()
{
	return m_Viewport;
}

void CRenderer::Submit(CPatch* patch)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
	{
		m->shadow.AddShadowReceiverBound(patch->GetWorldBounds());
		m->silhouetteRenderer.AddOccluder(patch);
	}

	if (m_CurrentCullGroup == CULL_SHADOWS)
	{
		m->shadow.AddShadowCasterBound(patch->GetWorldBounds());
	}

	m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
}

void CRenderer::Submit(SOverlayLine* overlay)
{
	// Overlays are only needed in the default cull group for now,
	// so just ignore submissions to any other group
	if (m_CurrentCullGroup == CULL_DEFAULT)
		m->overlayRenderer.Submit(overlay);
}

void CRenderer::Submit(SOverlayTexturedLine* overlay)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
		m->overlayRenderer.Submit(overlay);
}

void CRenderer::Submit(SOverlaySprite* overlay)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
		m->overlayRenderer.Submit(overlay);
}

void CRenderer::Submit(SOverlayQuad* overlay)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
		m->overlayRenderer.Submit(overlay);
}

void CRenderer::Submit(SOverlaySphere* overlay)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
		m->overlayRenderer.Submit(overlay);
}

void CRenderer::Submit(CModelDecal* decal)
{
	// Decals can't cast shadows since they're flat on the terrain.
	// They can receive shadows, but the terrain under them will have
	// already been passed to AddShadowCasterBound, so don't bother
	// doing it again here.

	m->terrainRenderer.Submit(m_CurrentCullGroup, decal);
}

void CRenderer::Submit(CParticleEmitter* emitter)
{
	m->particleRenderer.Submit(m_CurrentCullGroup, emitter);
}

void CRenderer::SubmitNonRecursive(CModel* model)
{
	if (m_CurrentCullGroup == CULL_DEFAULT)
	{
		m->shadow.AddShadowReceiverBound(model->GetWorldBounds());

		if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER)
			m->silhouetteRenderer.AddOccluder(model);
		if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY)
			m->silhouetteRenderer.AddCaster(model);
	}

	if (m_CurrentCullGroup == CULL_SHADOWS)
	{
		if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS))
			return;

		m->shadow.AddShadowCasterBound(model->GetWorldBounds());
	}

	bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);

	if (model->GetMaterial().UsesAlphaBlending())
	{
		if (requiresSkinning)
			m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model);
		else
			m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model);
	}
	else
	{
		if (requiresSkinning)
			m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model);
		else
			m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model);
	}
}


///////////////////////////////////////////////////////////
// Render the given scene
void CRenderer::RenderScene(Scene& scene)
{
	m_CurrentScene = &scene;

	CFrustum frustum = m_CullCamera.GetFrustum();

	m_CurrentCullGroup = CULL_DEFAULT;

	scene.EnumerateObjects(frustum, this);

	m->particleManager.RenderSubmit(*this, frustum);

	if (m_Options.m_Silhouettes)
	{
		m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera);

		m_CurrentCullGroup = CULL_DEFAULT;
		m->silhouetteRenderer.RenderSubmitOverlays(*this);

		m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER;
		m->silhouetteRenderer.RenderSubmitOccluders(*this);

		m_CurrentCullGroup = CULL_SILHOUETTE_CASTER;
		m->silhouetteRenderer.RenderSubmitCasters(*this);
	}

	if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER)
	{
		m_CurrentCullGroup = CULL_SHADOWS;

		CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum();
		scene.EnumerateObjects(shadowFrustum, this);
	}

	CBoundingBoxAligned waterScissor;
	if (m_WaterManager->m_RenderWater)
	{
		waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection());

		if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater())
		{
			if (m_Options.m_WaterReflection)
			{
				m_CurrentCullGroup = CULL_REFLECTIONS;

				CCamera reflectionCamera;
				ComputeReflectionCamera(reflectionCamera, waterScissor);

				scene.EnumerateObjects(reflectionCamera.GetFrustum(), this);
			}

			if (m_Options.m_WaterRefraction)
			{
				m_CurrentCullGroup = CULL_REFRACTIONS;

				CCamera refractionCamera;
				ComputeRefractionCamera(refractionCamera, waterScissor);

				scene.EnumerateObjects(refractionCamera.GetFrustum(), this);
			}
		}
		// Render the waves to the Fancy effects texture
		m_WaterManager->RenderWaves(frustum);
	}

	m_CurrentCullGroup = -1;

	ogl_WarnIfError();

	RenderSubmissions(waterScissor);

	m_CurrentScene = NULL;
}

Scene& CRenderer::GetScene()
{
	ENSURE(m_CurrentScene);
	return *m_CurrentScene;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BindTexture: bind a GL texture object to current active unit
void CRenderer::BindTexture(int unit, GLuint tex)
{
	pglActiveTextureARB(GL_TEXTURE0+unit);

	glBindTexture(GL_TEXTURE_2D, tex);
#if !CONFIG2_GLES
	if (tex) {
		glEnable(GL_TEXTURE_2D);
	} else {
		glDisable(GL_TEXTURE_2D);
	}
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
// calculate the coordinate of each alphamap within this packed texture
// NB: A variant of this function is duplicated in TerrainTextureEntry.cpp, for use with the Shader
// renderpath. This copy is kept to load the 'standard' maps for the fixed pipeline and should
// be removed if/when the fixed pipeline goes.
int CRenderer::LoadAlphaMaps()
{
	const wchar_t* const key = L"(alpha map composite)";
	Handle ht = ogl_tex_find(key);
	// alpha map texture had already been created and is still in memory:
	// reuse it, do not load again.
	if(ht > 0)
	{
		m_hCompositeAlphaMap = ht;
		return 0;
	}

	//
	// load all textures and store Handle in array
	//
	Handle textures[NumAlphaMaps] = {0};
	VfsPath path(L"art/textures/terrain/alphamaps/standard");
	const wchar_t* fnames[NumAlphaMaps] = {
		L"blendcircle.png",
		L"blendlshape.png",
		L"blendedge.png",
		L"blendedgecorner.png",
		L"blendedgetwocorners.png",
		L"blendfourcorners.png",
		L"blendtwooppositecorners.png",
		L"blendlshapecorner.png",
		L"blendtwocorners.png",
		L"blendcorner.png",
		L"blendtwoedges.png",
		L"blendthreecorners.png",
		L"blendushape.png",
		L"blendbad.png"
	};
	size_t base = 0;	// texture width/height (see below)
	// for convenience, we require all alpha maps to be of the same BPP
	// (avoids another ogl_tex_get_size call, and doesn't hurt)
	size_t bpp = 0;
	for(size_t i=0;i<NumAlphaMaps;i++)
	{
		// note: these individual textures can be discarded afterwards;
		// we cache the composite.
		textures[i] = ogl_tex_load(g_VFS, path / fnames[i]);
		RETURN_STATUS_IF_ERR(textures[i]);

		// get its size and make sure they are all equal.
		// (the packing algo assumes this)
		size_t this_width = 0, this_height = 0, this_bpp = 0;	// fail-safe
		(void)ogl_tex_get_size(textures[i], &this_width, &this_height, &this_bpp);
		if(this_width != this_height)
			DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
		// .. first iteration: establish size
		if(i == 0)
		{
			base = this_width;
			bpp  = this_bpp;
		}
		// .. not first: make sure texture size matches
		else if(base != this_width || bpp != this_bpp)
			DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
	}

	//
	// copy each alpha map (tile) into one buffer, arrayed horizontally.
	//
	size_t tile_w = 2+base+2;	// 2 pixel border (avoids bilinear filtering artifacts)
	size_t total_w = round_up_to_pow2(tile_w * NumAlphaMaps);
	size_t total_h = base; ENSURE(is_pow2(total_h));
	shared_ptr<u8> data;
	AllocateAligned(data, total_w*total_h, maxSectorSize);
	// for each tile on row
	for (size_t i = 0; i < NumAlphaMaps; i++)
	{
		// get src of copy
		u8* src = 0;
		(void)ogl_tex_get_data(textures[i], &src);

		size_t srcstep = bpp/8;

		// get destination of copy
		u8* dst = data.get() + (i*tile_w);

		// for each row of image
		for (size_t j = 0; j < base; j++)
		{
			// duplicate first pixel
			*dst++ = *src;
			*dst++ = *src;

			// copy a row
			for (size_t k = 0; k < base; k++)
			{
				*dst++ = *src;
				src += srcstep;
			}

			// duplicate last pixel
			*dst++ = *(src-srcstep);
			*dst++ = *(src-srcstep);

			// advance write pointer for next row
			dst += total_w-tile_w;
		}

		m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w);
		m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w);
		m_AlphaMapCoords[i].v0 = 0.0f;
		m_AlphaMapCoords[i].v1 = 1.0f;
	}

	for (size_t i = 0; i < NumAlphaMaps; i++)
		(void)ogl_tex_free(textures[i]);

	// upload the composite texture
	Tex t;
	(void)t.wrap(total_w, total_h, 8, TEX_GREY, data, 0);

	/*VfsPath filename("blendtex.png");

	DynArray da;
	RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));

	// write to disk
	//Status ret = INFO::OK;
	{
		shared_ptr<u8> file = DummySharedPtr(da.base);
		const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
		if(bytes_written > 0)
			ENSURE(bytes_written == (ssize_t)da.pos);
		//else
		//	ret = (Status)bytes_written;
	}

	(void)da_free(&da);*/

	m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key);
	(void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR);
	(void)ogl_tex_set_wrap  (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
	int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0);

	return ret;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps
void CRenderer::UnloadAlphaMaps()
{
	ogl_tex_free(m_hCompositeAlphaMap);
	m_hCompositeAlphaMap = 0;
}



Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path)
{
	CRenderer* renderer = static_cast<CRenderer*>(param);

	// If an alpha map changed, and we already loaded them, then reload them
	if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/"))
	{
		if (renderer->m_hCompositeAlphaMap)
		{
			renderer->UnloadAlphaMaps();
			renderer->LoadAlphaMaps();
		}
	}

	return INFO::OK;
}

void CRenderer::MakeShadersDirty()
{
	m->ShadersDirty = true;
	m_WaterManager->m_NeedsReloading = true;
}

CTextureManager& CRenderer::GetTextureManager()
{
	return m->textureManager;
}

CShaderManager& CRenderer::GetShaderManager()
{
	return m->shaderManager;
}

CParticleManager& CRenderer::GetParticleManager()
{
	return m->particleManager;
}

TerrainRenderer& CRenderer::GetTerrainRenderer()
{
	return m->terrainRenderer;
}

CTimeManager& CRenderer::GetTimeManager()
{
	return m->timeManager;
}

CMaterialManager& CRenderer::GetMaterialManager()
{
	return m->materialManager;
}

CPostprocManager& CRenderer::GetPostprocManager()
{
	return m->postprocManager;
}

CFontManager& CRenderer::GetFontManager()
{
	return m->fontManager;
}

ShadowMap& CRenderer::GetShadowMap()
{
	return m->shadow;
}

void CRenderer::ResetState()
{
	// Clear all emitters, that were created in previous games
	GetParticleManager().ClearUnattachedEmitters();
}
