/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "gpu/gpu_renderer.h"
		#include "core/renderer_environment_interface.h"
		#include "core/renderer_resource_factory.h"
		#include "core/camera.h"

		using namespace GS;
		using namespace GS::GPU;
		using Render::TextureParm;


//------------------------------------------------------------------------------
void				Renderer::SetViewMatrix(const Matrix4 &m, const Matrix4 *inverse)
{
	m_view = m;
	m_iview = inverse ? *inverse : m.InversedFast();
}
void				Renderer::SetProjectionMatrix(const Matrix4 &m)
{	m_projection = m;	}
void				Renderer::SetWorldMatrix(const Matrix4 &m, const Matrix4 *inverse)
{
	m_world = m;
	m_iworld = inverse ? *inverse : m.InversedFast();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
int					Renderer::GetAnisotropySampleCount(TextureParm::Anisotropy aniso) const
{
	uint sample = 1;

	switch (aniso)
	{
		default:
			sample = registry.GetInteger("Texture:Filtering:Sample", 4);
			break;

		case TextureParm::AnisotropyNone:	sample = 1;		break;
		case TextureParm::Anisotropy2x:		sample = 2;		break;
		case TextureParm::Anisotropy4x:		sample = 4;		break;
		case TextureParm::Anisotropy8x:		sample = 8;		break;
		case TextureParm::Anisotropy16x:	sample = 16;	break;
	}

	if	(sample > gpu_config.max_anisotropy)
		sample = gpu_config.max_anisotropy;

	return sample;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::SetDisplayList(DisplayList *dlist)
{
	SetIndexSource(dlist ? dlist->idx.c_ptr() : NULL);
	SetVertexSource(dlist ? dlist->vtx.c_ptr() : NULL, dlist ? dlist->stride : 0);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::SetMaterial(const GPU::Material &m, const DrawContext::Context &ctx, bool force_alpha)
{
	bool fog_enabled = (environment_interface != NULL) && environment_interface->IsFogEnabled();

	if	(m.renderword & Core::Material::Render_DoubleSided)
		EnableCulling(false);

	if	(m.renderword & Core::Material::Render_AlphaTest)
		if	(gpu_config.enable_aa)
			EnableAlphaToCoverage(true);

	uint blendop = m.blendop;
	if	((blendop == Core::Material::Blend_None) && force_alpha)
		blendop = Core::Material::Blend_Alpha;

	switch (ctx.render)
	{
		case DrawContext::Deferred:
		case DrawContext::Opaque:
			break;

		case DrawContext::Alpha:
			switch (ctx.draw)
			{
				case DrawContext::Base:
					switch (blendop)
					{
						case Core::Material::Blend_Add:
							if	(fog_enabled)
								SetBlendFunc(BlendSrcAlpha, BlendOne);
							else
								SetBlendFunc(BlendOne, BlendOne);
							break;

						case Core::Material::Blend_Alpha:
							SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha);
							break;
					}
					break;

				case DrawContext::Light:
					switch (blendop)
					{
						case Core::Material::Blend_Add:
							if	(fog_enabled)
								SetBlendFunc(BlendSrcAlpha, BlendOne);
							else
								SetBlendFunc(BlendOne, BlendOne);
							break;

						case Core::Material::Blend_Alpha:
							SetBlendFunc(BlendSrcAlpha, BlendOne);
							break;
					}
					break;
			}
	}
}
void				Renderer::UnsetMaterial(const GPU::Material &m, const DrawContext::Context &ctx)
{
	if	(m.renderword & Core::Material::Render_DoubleSided)
		EnableCulling(true);

	if	(m.renderword & Core::Material::Render_AlphaTest)
		if	(gpu_config.enable_aa)
			EnableAlphaToCoverage(false);

	switch (ctx.render)
	{
		case DrawContext::Deferred:
		case DrawContext::Opaque:
			break;

		case DrawContext::Alpha:
			switch (ctx.draw)
			{
				case DrawContext::Base:
					SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha);
					break;

				case DrawContext::Light:
					SetBlendFunc(BlendSrcAlpha, BlendOne);
					break;
			}
			break;
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::DrawSetState(Core::Material::BlendOperator bo, Core::Material::RenderWord f)
{
	switch (bo)
	{
		default:
		case Core::Material::Blend_None:
			break;

		case Core::Material::Blend_Alpha:
			EnableBlending(true);
			SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha);
			break;
		case Core::Material::Blend_Add:
			EnableBlending(true);
			SetBlendFunc(BlendSrcAlpha, BlendOne);
			break;
	}

	EnableDepthWrite(!asbool(f & Core::Material::Render_NoZWrite));
	EnableDepthTest(!asbool(f & Core::Material::Render_NoZTest));
	EnableCulling(!asbool(f & Core::Material::Render_DoubleSided));
}
void				Renderer::DrawRestoreState(Core::Material::BlendOperator bo, Core::Material::RenderWord f)
{
	EnableCulling(true);
	EnableDepthTest(true);
	EnableDepthWrite(true);

	if	(bo != Core::Material::Blend_None)
	{
		EnableBlending(false);
		SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha);
	}
}
void				Renderer::DrawLine(uint count, const Vector4 *v, const Color *c, Core::Material::BlendOperator bo, Core::Material::RenderWord f, Render::Shader *r_p)
{
	DrawSetState(bo, f);

	// Dispatch inputs to the correct program.
	Shader *p = (Shader *)r_p;

	if	(p == NULL)
	{
		p = c ? single_color_program : simple_program;

		if	(!p)
			return;
	}
	SetShaderProgram(p);

	ShaderInput *vtx_parm = p->GetInput(Core::ShaderInput::Position),
				*color_parm = p->GetInput(Core::ShaderInput::VertexColor);

	SetIndexSource(NULL);		// FIXME broken on the DirectX back-end
	SetVertexSource(NULL, 0);	// FIXME broken on the DirectX back-end

	if	(vtx_parm)
		p->Set(*vtx_parm->location, 3, Types::ValueFloat, false, sizeof(Vector4), (const void *)v);
	if	(color_parm && c)
		p->Set(*color_parm->location, 4, Types::ValueFloat, false, sizeof(Color), (const void *)c);

	p->SetRendererInputs(*this);
	p->SetTransformInputs(m_projection, m_view, m_iview, &m_world, &m_iworld);

	DrawElements(Types::PrimitiveLine, 2 * count);

	if	(vtx_parm)
		p->Set(*vtx_parm->location, 0, Types::ValueLast, false, 0, NULL);
	if	(color_parm)
		p->Set(*color_parm->location, 0, Types::ValueLast, false, 0, NULL);

	DrawRestoreState(bo, f);
}
void				Renderer::DrawTriangle(uint count, const Vector4 *v, const ushort *idx, const Color *c, const Vector2 *uv, const Render::Texture *t, Core::Material::BlendOperator bo, Core::Material::RenderWord f, Render::Shader *r_p)
{
	DrawSetState(bo, f);

	// Dispatch inputs to the correct program.
	Shader *p = (Shader *)r_p;

	if	(p == NULL)
	{
		if	(c)
			p = t ? single_texture_color_program : single_color_program;
		else
			if	(t)
				p = single_texture_program;

		if	(!p)
			return;
	}
	SetShaderProgram(p);

	// If no indice were provided, assume a linear attribute array.
	Array <ushort> indice;
	if	(!idx)
		if	(indice.Allocate(count * 3))
		{
			idx = indice;
			for	(uint n = 0; n < count * 3; ++n)
				indice[n] = ushort(n);
		}

	// Set program inputs.
	ShaderInput	*vtx_parm = p->GetInput(Core::ShaderInput::Position),
				*uv_parm = p->GetInput(Core::ShaderInput::UV0),
				*color_parm = p->GetInput(Core::ShaderInput::VertexColor),
				*texture_parm = p->GetInput(Core::ShaderInput::Texture2D);

	if	(gpu_config.can_stream_vertex_from_memory)
	{
		SetIndexSource(NULL);
		SetVertexSource(NULL, 0);

		if	(vtx_parm)
			p->Set(*vtx_parm->location, 3, Types::ValueFloat, false, sizeof(Vector4), (const void *)v);
		if	(uv_parm && uv)
			p->Set(*uv_parm->location, 2, Types::ValueFloat, false, sizeof(Vector2), (const void *)uv);
		if	(color_parm && c)
			p->Set(*color_parm->location, 4, Types::ValueFloat, false, sizeof(Color), (const void *)c);
	}
	else
	{
		DirectVertexLayout layout;
		BuildDirectVertexLayout(count * 3, idx, v, c, uv, layout, direct_idx_vbo, direct_vtx_vbo);

		SetIndexSource(direct_idx_vbo);
		SetVertexSource(direct_vtx_vbo, layout.stride);

		if	(vtx_parm)
			p->Set(*vtx_parm->location, 3, Types::ValueFloat, false, layout.stride, (const void *)layout.vtx_offset);
		if	(uv_parm && uv)
			p->Set(*uv_parm->location, 2, Types::ValueFloat, false, layout.stride, (const void *)layout.uv_offset);
		if	(color_parm && c)
			p->Set(*color_parm->location, 4, Types::ValueUByte, true, layout.stride, (const void *)layout.color_offset);
	}

	if	(texture_parm && t)
		p->Set(*texture_parm->location, *t, texture_parm->index);

	p->SetRendererInputs(*this);
	p->SetTransformInputs(m_projection, m_view, m_iview, &m_world, &m_iworld);
	p->CommitInputs();

	DrawElements(Types::PrimitiveTriangle, count * 3, Types::ValueUShort, gpu_config.can_stream_vertex_from_memory ? idx : 0);

	if	(vtx_parm)
		p->Set(*vtx_parm->location, 0, Types::ValueLast, false, 0, NULL);
	if	(uv_parm)
		p->Set(*uv_parm->location, 0, Types::ValueLast, false, 0, NULL);
	if	(color_parm)
		p->Set(*color_parm->location, 0, Types::ValueLast, false, 0, NULL);

	DrawRestoreState(bo, f);
	stats.triangle_drawn += count;
}
void				Renderer::DrawSprite(uint count, const Vector4 *v, const Color *c, const float *s, const Render::Texture *t, float g_size, Core::Material::BlendOperator bo, Core::Material::RenderWord f, Render::Shader *r_p)
{
	Array <Vector4> sprite_p;
	Array <Color> sprite_c;
	Array <Vector2> sprite_uv;
	Array <ushort> sprite_idx;

	Shader *p = (Shader *)r_p;

	if	(p == NULL)
		p = simple_program;

	// Setup display list.
	if	(!sprite_p.Allocate(count * 4) || !sprite_idx.Allocate(count * 3 * 2))
		return;

	Vector4 *p_v = sprite_p.c_ptr();
	ushort *idx = sprite_idx.c_ptr();

	Vector4 left = m_view.GetRow(0),
			up = m_view.GetRow(1);

	if (s)
		for	(uint n = 0; n < count; ++n)
		{
			register float k = g_size * s[n];
			Vector4	uml = (up - left) * k;
			Vector4	lpu = (left + up) * k;
			Vector4	lmu = (left - up) * k;
			*p_v++ = v[n] + uml;
			*p_v++ = v[n] + lpu;
			*p_v++ = v[n] + lmu;
			*p_v++ = v[n] - lpu;
		}
	else
		for	(uint n = 0; n < count; ++n)
		{
			Vector4	uml = (up - left) * g_size;
			Vector4	lpu = (left + up) * g_size;
			Vector4	lmu = (left - up) * g_size;
			*p_v++ = v[n] + uml;
			*p_v++ = v[n] + lpu;
			*p_v++ = v[n] + lmu;
			*p_v++ = v[n] - lpu;
		}

	for	(ushort n = 0; n < count; ++n)
	{
		ushort s = (ushort)(n << 2);
		*idx++ = s; *idx++ = s + 1; *idx++ = s + 2;
		*idx++ = s; *idx++ = s + 2; *idx++ = s + 3;
	}

	if	(c)		// Color.
	{
		if	(!sprite_c.Allocate(count * 4))
			return;

		Color *p_c = sprite_c.c_ptr();
		for	(uint n = 0; n < count; ++n)
		{
			const Color &cl = c[n];
			for	(uint i = 0; i < 4; ++i)
				*p_c++ = cl;
		}

		p = single_color_program;
	}

	if	(t)		// Texture.
	{
		if	(!sprite_uv.Allocate(count * 4))
			return;

		Vector2 *p_uv = sprite_uv.c_ptr();
		for	(uint n = 0; n < count; ++n)
		{
			p_uv[0].Set(0, 0); p_uv[1].Set(1, 0);
			p_uv[2].Set(1, 1); p_uv[3].Set(0, 1);
			p_uv += 4;
		}

		p = c ? single_texture_color_program : single_texture_program;
	}

	// Compute aspect ratio.
	Matrix4 m_ar;
	if	(view_item)
			m_ar = Matrix4::ScaleMatrix(view_item->ComputeAspectRatioCorrection(viewport));
	else	m_ar = Matrix4::ScaleMatrix(Vector4(viewport.GetHeight() / viewport.GetWidth(), 1, 0, 1));

	// Set program inputs.
	DrawSetState(bo, f);
	SetShaderProgram(p);

	ShaderInput *vtx_parm = p->GetInput(Core::ShaderInput::Position),
				*uv_parm = p->GetInput(Core::ShaderInput::UV0),
				*color_parm = p->GetInput(Core::ShaderInput::VertexColor),
				*texture_parm = p->GetInput(Core::ShaderInput::Texture2D);

	if	(vtx_parm)
		p->Set(*vtx_parm->location, 3, Types::ValueFloat, false, sizeof(Vector4), (const void *)sprite_p.c_ptr());
	if	(uv_parm && t)
		p->Set(*uv_parm->location, 2, Types::ValueFloat, false, sizeof(Vector2), (const void *)sprite_uv.c_ptr());
	if	(color_parm && c)
		p->Set(*color_parm->location, 4, Types::ValueFloat, false, sizeof(Color), (const void *)sprite_c.c_ptr());
	if	(texture_parm && t)
		p->Set(*texture_parm->location, *t, texture_parm->index);

	p->SetRendererInputs(*this);
	p->SetTransformInputs(m_projection, m_view, m_iview, &m_world, &m_iworld);
	p->CommitInputs();

	DrawElements(Types::PrimitiveTriangle, sprite_idx.GetCount(), Types::ValueUShort, sprite_idx.c_ptr());

	if	(vtx_parm)
		p->Set(*vtx_parm->location, 0, Types::ValueLast, false, 0, NULL);
	if	(uv_parm)
		p->Set(*uv_parm->location, 0, Types::ValueLast, false, 0, NULL);
	if	(color_parm)
		p->Set(*color_parm->location, 0, Types::ValueLast, false, 0, NULL);

	DrawRestoreState(bo, f);
	stats.triangle_drawn += count * 2;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Renderer::Renderer() : terrain_patch_vtx(Alloc::RendererTerrain), terrain_patch_cache(Alloc::RendererTerrain)
{
	core_resource_factory = new Render::RendererResourceFactory(*this);

	pending_shadow_map_refresh = false;
	pending_core_shader_refresh = false;

	render_technique = TechniqueForward;

	m_view = Matrix4::IdentityMatrix();
	m_iview = Matrix4::IdentityMatrix();
	m_world = Matrix4::IdentityMatrix();
	m_iworld = Matrix4::IdentityMatrix();

	m_projection = Matrix4::IdentityMatrix();

	pcf_radius = 1.75f;
	frame_clock = 0.f;

	clipping.Set(-1, -1, -1, -1);
	output_fbo = NULL;
}
//------------------------------------------------------------------------------
