/* Copyright (C) 2013 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/>.
 */

/*
 * Shadow mapping related texture and matrix management
 */

#include "precompiled.h"

#include "gui/GUIutil.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"

#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"

#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"

#include "renderer/Renderer.h"
#include "renderer/ShadowMap.h"


///////////////////////////////////////////////////////////////////////////////////////////////////
// ShadowMap implementation

/**
 * Struct ShadowMapInternals: Internal data for the ShadowMap implementation
 */
struct ShadowMapInternals
{
	// bit depth for the depth texture
	int DepthTextureBits;
	// the EXT_framebuffer_object framebuffer
	GLuint Framebuffer;
	// handle of shadow map
	GLuint Texture;
	// width, height of shadow map
	int Width, Height;
	// used width, height of shadow map
	int EffectiveWidth, EffectiveHeight;
	// transform light space into projected light space
	// in projected light space, the shadowbound box occupies the [-1..1] cube
	// calculated on BeginRender, after the final shadow bounds are known
	CMatrix3D LightProjection;
	// Transform world space into light space; calculated on SetupFrame
	CMatrix3D LightTransform;
	// Transform world space into texture space of the shadow map;
	// calculated on BeginRender, after the final shadow bounds are known
	CMatrix3D TextureMatrix;

	// transform light space into world space
	CMatrix3D InvLightTransform;
	// bounding box of shadowed objects in light space
	CBoundingBoxAligned ShadowCasterBound;
	CBoundingBoxAligned ShadowReceiverBound;

	CBoundingBoxAligned ShadowRenderBound;

	// Camera transformed into light space
	CCamera LightspaceCamera;

	// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
	// incorrectly when the FBO has only a depth attachment.
	// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
	// alpha texture which is attached to the FBO as a workaround.
	GLuint DummyTexture;

	// Copy of renderer's standard view camera, saved between
	// BeginRender and EndRender while we replace it with the shadow camera
	CCamera SavedViewCamera;
	
	// Save the caller's FBO so it can be restored
	GLint SavedViewFBO;

	// Helper functions
	void CalcShadowMatrices();
	void CreateTexture();
};


///////////////////////////////////////////////////////////////////////////////////////////////////
// Construction/Destruction
ShadowMap::ShadowMap()
{
	m = new ShadowMapInternals;
	m->Framebuffer = 0;
	m->Texture = 0;
	m->DummyTexture = 0;
	m->Width = 0;
	m->Height = 0;
	m->EffectiveWidth = 0;
	m->EffectiveHeight = 0;
	m->DepthTextureBits = 0;
	// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
	// but they're very much slower on Radeon 9800.
	// In both cases, the default (no specified depth) is fast, so we just use
	// that by default and hope it's alright. (Otherwise, we'd probably need to
	// do some kind of hardware detection to work out what to use.)

	// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
	m->LightTransform.SetIdentity();
}


ShadowMap::~ShadowMap()
{
	if (m->Texture)
		glDeleteTextures(1, &m->Texture);
	if (m->DummyTexture)
		glDeleteTextures(1, &m->DummyTexture);
	if (m->Framebuffer)
		pglDeleteFramebuffersEXT(1, &m->Framebuffer);

	delete m;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
// size has changed
void ShadowMap::RecreateTexture()
{
	if (m->Texture)
		glDeleteTextures(1, &m->Texture);
	if (m->DummyTexture)
		glDeleteTextures(1, &m->DummyTexture);
	if (m->Framebuffer)
		pglDeleteFramebuffersEXT(1, &m->Framebuffer);

	m->Texture = 0;
	m->DummyTexture = 0;
	m->Framebuffer = 0;

	// (Texture will be constructed in next SetupFrame)
}

//////////////////////////////////////////////////////////////////////////////
// SetupFrame: camera and light direction for this frame
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
{
	if (!m->Texture)
		m->CreateTexture();

	CVector3D z = lightdir;
	CVector3D y;
	CVector3D x = camera.m_Orientation.GetIn();
	CVector3D eyepos = camera.m_Orientation.GetTranslation();

	z.Normalize();
	x -= z * z.Dot(x);
	if (x.Length() < 0.001)
	{
		// this is invoked if the camera and light directions almost coincide
		// assumption: light direction has a significant Z component
		x = CVector3D(1.0, 0.0, 0.0);
		x -= z * z.Dot(x);
	}
	x.Normalize();
	y = z.Cross(x);

	// X axis perpendicular to light direction, flowing along with view direction
	m->LightTransform._11 = x.X;
	m->LightTransform._12 = x.Y;
	m->LightTransform._13 = x.Z;

	// Y axis perpendicular to light and view direction
	m->LightTransform._21 = y.X;
	m->LightTransform._22 = y.Y;
	m->LightTransform._23 = y.Z;

	// Z axis is in direction of light
	m->LightTransform._31 = z.X;
	m->LightTransform._32 = z.Y;
	m->LightTransform._33 = z.Z;

	// eye is at the origin of the coordinate system
	m->LightTransform._14 = -x.Dot(eyepos);
	m->LightTransform._24 = -y.Dot(eyepos);
	m->LightTransform._34 = -z.Dot(eyepos);

	m->LightTransform._41 = 0.0;
	m->LightTransform._42 = 0.0;
	m->LightTransform._43 = 0.0;
	m->LightTransform._44 = 1.0;

	m->LightTransform.GetInverse(m->InvLightTransform);
	m->ShadowCasterBound.SetEmpty();
	m->ShadowReceiverBound.SetEmpty();

	//
	m->LightspaceCamera = camera;
	m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
	m->LightspaceCamera.UpdateFrustum();
}


//////////////////////////////////////////////////////////////////////////////
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds)
{
	CBoundingBoxAligned lightspacebounds;

	bounds.Transform(m->LightTransform, lightspacebounds);
	m->ShadowCasterBound += lightspacebounds;
}

void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
{
	CBoundingBoxAligned lightspacebounds;

	bounds.Transform(m->LightTransform, lightspacebounds);
	m->ShadowReceiverBound += lightspacebounds;
}

CFrustum ShadowMap::GetShadowCasterCullFrustum()
{
	// Get the bounds of all objects that can receive shadows
	CBoundingBoxAligned bound = m->ShadowReceiverBound;

	// Intersect with the camera frustum, so the shadow map doesn't have to get
	// stretched to cover the off-screen parts of large models
	bound.IntersectFrustumConservative(m->LightspaceCamera.GetFrustum());

	// ShadowBound might have been empty to begin with, producing an empty result
	if (bound.IsEmpty())
	{
		// CFrustum can't easily represent nothingness, so approximate it with
		// a single point which won't match many objects
		bound += CVector3D(0.0f, 0.0f, 0.0f);
		return bound.ToFrustum();
	}

	// Extend the bounds a long way towards the light source, to encompass
	// all objects that might cast visible shadows.
	// (The exact constant was picked entirely arbitrarily.)
	bound[0].Z -= 1000.f;

	CFrustum frustum = bound.ToFrustum();
	frustum.Transform(m->InvLightTransform);
	return frustum;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// CalcShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void ShadowMapInternals::CalcShadowMatrices()
{
	// Start building the shadow map to cover all objects that will receive shadows
	CBoundingBoxAligned receiverBound = ShadowReceiverBound;

	// Intersect with the camera frustum, so the shadow map doesn't have to get
	// stretched to cover the off-screen parts of large models
	receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());

	// Intersect with the shadow caster bounds, because there's no point
	// wasting space around the edges of the shadow map that we're not going
	// to draw into
	ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X);
	ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y);
	ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X);
	ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y);

	// Set the near and far planes to include just the shadow casters,
	// so we make full use of the depth texture's range. Add a bit of a
	// delta so we don't accidentally clip objects that are directly on
	// the planes.
	ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f;
	ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f;

	// ShadowBound might have been empty to begin with, producing an empty result
	if (ShadowRenderBound.IsEmpty())
	{
		// no-op
		LightProjection.SetIdentity();
		TextureMatrix = LightTransform;
		return;
	}

	// round off the shadow boundaries to sane increments to help reduce swim effect
	float boundInc = 16.0f;
	ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc;
	ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc;
	ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc;
	ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc;

	// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
	CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0];
	CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5;

	if (scale.X < 1.0)
		scale.X = 1.0;
	if (scale.Y < 1.0)
		scale.Y = 1.0;
	if (scale.Z < 1.0)
		scale.Z = 1.0;

	scale.X = 2.0 / scale.X;
	scale.Y = 2.0 / scale.Y;
	scale.Z = 2.0 / scale.Z;

	// make sure a given world position falls on a consistent shadowmap texel fractional offset
	float offsetX = fmod(ShadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
	float offsetY = fmod(ShadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));

	LightProjection.SetZero();
	LightProjection._11 = scale.X;
	LightProjection._14 = (shift.X + offsetX) * scale.X;
	LightProjection._22 = scale.Y;
	LightProjection._24 = (shift.Y + offsetY) * scale.Y;
	LightProjection._33 = scale.Z;
	LightProjection._34 = shift.Z * scale.Z;
	LightProjection._44 = 1.0;

	// Calculate texture matrix by creating the clip space to texture coordinate matrix
	// and then concatenating all matrices that have been calculated so far

	float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
	float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
	float texscalez = scale.Z * 0.5f;

	CMatrix3D lightToTex;
	lightToTex.SetZero();
	lightToTex._11 = texscalex;
	lightToTex._14 = (offsetX - ShadowRenderBound[0].X) * texscalex;
	lightToTex._22 = texscaley;
	lightToTex._24 = (offsetY - ShadowRenderBound[0].Y) * texscaley;
	lightToTex._33 = texscalez;
	lightToTex._34 = -ShadowRenderBound[0].Z * texscalez;
	lightToTex._44 = 1.0;

	TextureMatrix = lightToTex * LightTransform;
}



//////////////////////////////////////////////////////////////////////////
// Create the shadow map
void ShadowMapInternals::CreateTexture()
{
	// Cleanup
	if (Texture)
	{
		glDeleteTextures(1, &Texture);
		Texture = 0;
	}
	if (DummyTexture)
	{
		glDeleteTextures(1, &DummyTexture);
		DummyTexture = 0;
	}
	if (Framebuffer)
	{
		pglDeleteFramebuffersEXT(1, &Framebuffer);
		Framebuffer = 0;
	}
	
	// save the caller's FBO	
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO);

	pglGenFramebuffersEXT(1, &Framebuffer);

	if (g_Renderer.m_ShadowMapSize != 0)
	{
		// non-default option to override the size
		Width = Height = g_Renderer.m_ShadowMapSize;
	}
	else
	{
		// get shadow map size as next power of two up from view width/height
		Width = Height = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight()));
	}
	// Clamp to the maximum texture size
	Width = std::min(Width, (int)ogl_max_tex_size);
	Height = std::min(Height, (int)ogl_max_tex_size);

	// Since we're using a framebuffer object, the whole texture is available
	EffectiveWidth = Width;
	EffectiveHeight = Height;

	const char* formatname;

	switch(DepthTextureBits)
	{
	case 16: formatname = "DEPTH_COMPONENT16"; break;
	case 24: formatname = "DEPTH_COMPONENT24"; break;
	case 32: formatname = "DEPTH_COMPONENT32"; break;
	default: formatname = "DEPTH_COMPONENT"; break;
	}

	LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
		Width, Height, formatname);


	if (g_Renderer.m_Options.m_ShadowAlphaFix)
	{
		glGenTextures(1, &DummyTexture);
		g_Renderer.BindTexture(0, DummyTexture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	}

	glGenTextures(1, &Texture);
	g_Renderer.BindTexture(0, Texture);

	GLenum format;

#if CONFIG2_GLES
	format = GL_DEPTH_COMPONENT;
#else
	switch (DepthTextureBits)
	{
	case 16: format = GL_DEPTH_COMPONENT16; break;
	case 24: format = GL_DEPTH_COMPONENT24; break;
	case 32: format = GL_DEPTH_COMPONENT32; break;
	default: format = GL_DEPTH_COMPONENT; break;
	}
#endif

	glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
	// GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT

	// set texture parameters
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

#if CONFIG2_GLES
	// GLES doesn't do depth comparisons, so treat it as a
	// basic unfiltered depth texture
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#else
	// Enable automatic depth comparisons
	glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

	// Use GL_LINEAR to trigger automatic PCF on some devices
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#endif

	ogl_WarnIfError();

	// bind to framebuffer object
	glBindTexture(GL_TEXTURE_2D, 0);
	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer);

	pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture, 0);

	if (g_Renderer.m_Options.m_ShadowAlphaFix)
	{
		pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0);
	}
	else
	{
#if CONFIG2_GLES
#warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES
#else
		glDrawBuffer(GL_NONE);
#endif
	}

#if !CONFIG2_GLES
	glReadBuffer(GL_NONE);
#endif

	ogl_WarnIfError();

	GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

	pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO);

	if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
	{
		LOGWARNING("Framebuffer object incomplete: 0x%04X", status);

		// Disable shadow rendering (but let the user try again if they want)
		g_Renderer.m_Options.m_Shadows = false;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up to render into shadow map texture
void ShadowMap::BeginRender()
{
	// HACK HACK: this depends in non-obvious ways on the behaviour of the caller
	
	// save caller's FBO
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &m->SavedViewFBO);

	// Calc remaining shadow matrices
	m->CalcShadowMatrices();

	{
		PROFILE("bind framebuffer");
		glBindTexture(GL_TEXTURE_2D, 0);
		pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->Framebuffer);
	}

	// clear buffers
	{
		PROFILE("clear depth texture");
		// In case we used m_ShadowAlphaFix, we ought to clear the unused
		// color buffer too, else Mali 400 drivers get confused.
		// Might as well clear stencil too for completeness.
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		glColorMask(0,0,0,0);
	}

	// setup viewport
	const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
	g_Renderer.SetViewport(vp);

	m->SavedViewCamera = g_Renderer.GetViewCamera();

	CCamera c = m->SavedViewCamera;
	c.SetProjection(m->LightProjection);
	c.GetOrientation() = m->InvLightTransform;
	g_Renderer.SetViewCamera(c);

#if !CONFIG2_GLES
	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(&m->LightProjection._11);
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(&m->LightTransform._11);
#endif

	glEnable(GL_SCISSOR_TEST);
	glScissor(1,1, m->EffectiveWidth-2, m->EffectiveHeight-2);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Finish rendering into shadow map texture
void ShadowMap::EndRender()
{
	glDisable(GL_SCISSOR_TEST);

	g_Renderer.SetViewCamera(m->SavedViewCamera);

	{
		PROFILE("unbind framebuffer");
		pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->SavedViewFBO);
	}

	const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
	g_Renderer.SetViewport(vp);

	glColorMask(1,1,1,1);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Retrieve the texture handle and texture matrix for shadowing
GLuint ShadowMap::GetTexture() const
{
	return m->Texture;
}

const CMatrix3D& ShadowMap::GetTextureMatrix() const
{
	return m->TextureMatrix;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Depth texture bits
int ShadowMap::GetDepthTextureBits() const
{
	return m->DepthTextureBits;
}

void ShadowMap::SetDepthTextureBits(int bits)
{
	if (bits != m->DepthTextureBits)
	{
		if (m->Texture)
		{
			glDeleteTextures(1, &m->Texture);
			m->Texture = 0;
		}
		m->Width = m->Height = 0;

		m->DepthTextureBits = bits;
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Depth texture size
int ShadowMap::GetWidth() const
{
	return m->Width;
}

int ShadowMap::GetHeight() const
{
	return m->Height;
}

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

void ShadowMap::RenderDebugBounds()
{
	CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
	shaderTech->BeginPass();
	CShaderProgramPtr shader = shaderTech->GetShader();

	glDepthMask(0);
	glDisable(GL_CULL_FACE);

	// Render various shadow bounds:
	//  Yellow = bounds of objects in view frustum that receive shadows
	//  Red = culling frustum used to find potential shadow casters
	//  Green = bounds of objects in culling frustum that cast shadows
	//  Blue = frustum used for rendering the shadow map

	shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection() * m->InvLightTransform);

	shader->Uniform(str_color, 1.0f, 1.0f, 0.0f, 1.0f);
	m->ShadowReceiverBound.RenderOutline(shader);

	shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f);
	m->ShadowCasterBound.RenderOutline(shader);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 0.25f);
	m->ShadowRenderBound.Render(shader);
	glDisable(GL_BLEND);

	shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 1.0f);
	m->ShadowRenderBound.RenderOutline(shader);

	// Render light frustum

	shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());

	CFrustum frustum = GetShadowCasterCullFrustum();
	// We don't have a function to create a brush directly from a frustum, so use
	// the ugly approach of creating a large cube and then intersecting with the frustum
	CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
	CBrush brush(dummy);
	CBrush frustumBrush;
	brush.Intersect(frustum, frustumBrush);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 0.25f);
	frustumBrush.Render(shader);
	glDisable(GL_BLEND);

	shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 1.0f);
	frustumBrush.RenderOutline(shader);


	shaderTech->EndPass();

#if 0
	CMatrix3D InvTexTransform;

	m->TextureMatrix.GetInverse(InvTexTransform);

	// Render representative texture rectangle
	glPushMatrix();
	glMultMatrixf(&InvTexTransform._11);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4ub(255,0,0,64);
	glBegin(GL_QUADS);
		glVertex3f(0.0, 0.0, 0.0);
		glVertex3f(1.0, 0.0, 0.0);
		glVertex3f(1.0, 1.0, 0.0);
		glVertex3f(0.0, 1.0, 0.0);
	glEnd();
	glDisable(GL_BLEND);

	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor3ub(255,0,0);
	glBegin(GL_QUADS);
		glVertex3f(0.0, 0.0, 0.0);
		glVertex3f(1.0, 0.0, 0.0);
		glVertex3f(1.0, 1.0, 0.0);
		glVertex3f(0.0, 1.0, 0.0);
	glEnd();
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glPopMatrix();
#endif

	glEnable(GL_CULL_FACE);
	glDepthMask(1);
}

void ShadowMap::RenderDebugTexture()
{
	glDepthMask(0);

	glDisable(GL_DEPTH_TEST);

#if !CONFIG2_GLES
	g_Renderer.BindTexture(0, m->Texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
#endif

	CShaderTechniquePtr texTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_basic);
	texTech->BeginPass();
	CShaderProgramPtr texShader = texTech->GetShader();

	texShader->Uniform(str_transform, GetDefaultGuiMatrix());
	texShader->BindTexture(str_tex, m->Texture);

	float s = 256.f;
	float boxVerts[] = {
 		0,0, 0,s, s,0,
		s,0, 0,s, s,s
	};
	float boxUV[] = {
		0,0, 0,1, 1,0,
		1,0, 0,1, 1,1
	};

	texShader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
	texShader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, boxUV);
	texShader->AssertPointersBound();
	glDrawArrays(GL_TRIANGLES, 0, 6);

	texTech->EndPass();

#if !CONFIG2_GLES
	g_Renderer.BindTexture(0, m->Texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
#endif

	glEnable(GL_DEPTH_TEST);
	glDepthMask(1);
}
