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


		#include "gpu/gpu_renderer.h"
		#include "log/log.h"

		using namespace GS::GPU;


//------------------------------------------------------------------------------
Renderer::RenderTechnique Renderer::GetDefaultRenderTechnique() const
{
	String technique = registry.GetString("Technique", "Forward");

	if	(technique == "Deferred")
		return TechniqueDeferred;

	return TechniqueForward;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::FreeRenderTechnique()
{
	for	(uint n = 0; n < 4; ++n)
		t_gbuffer[n] = NULL;
	t_depth = NULL;

	t_color_aa = NULL;
	t_depth_aa = NULL;
}
void				Renderer::SetRenderTechnique(RenderTechnique p)
{
	if	(p == TechniqueDefault)
		p = GetDefaultRenderTechnique();

	// Filter out illegal configurations.
	if	(!gpu_config.use_rtt && (p == TechniqueDeferred))
		p = TechniqueForward;

	FreeRenderTechnique();
	render_technique = p;

	// Select AA method.
	Render::Texture::AA aa = Render::Texture::NoAA;

	if	((p == TechniqueForward) && gpu_config.can_resolve_msaa)
		if	(registry.GetBool("Antialiasing:Enable", false))
		{
			float sample = registry.GetReal("Antialiasing:Sample", 4.0);

					if	(sample > 8.f)	aa = Render::Texture::MSAA16x;
			else	if	(sample > 4.f)	aa = Render::Texture::MSAA8x;
			else	if	(sample > 2.f)	aa = Render::Texture::MSAA4x;
			else	if	(sample > 1.f)	aa = Render::Texture::MSAA2x;
		}

	gpu_config.enable_aa = asbool(aa != Render::Texture::NoAA);

	// Setup technique objects.
	__LOG__ << "SetRenderTechnique(): " << dimensions.x << "x" << dimensions.y << "\n";

	bool use_float = registry.GetBool("Texture:Float:Enable", false);
	Render::Texture::Format t_format = use_float ? Render::Texture::FormatRGBAF : Render::Texture::FormatRGBA8;

	for	(uint n = 0; n < 2; ++n)
	{
		t_compose[n] = NewTexture(String("t_compose") << n);
		t_compose[n]->Create(NULL, dimensions.x, dimensions.y, t_format, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
		t_compose[n]->SetFiltering(Render::TextureParm::FilterTrilinear);
		t_compose[n]->SetWrapping(Render::TextureParm::WrapClamp, Render::TextureParm::WrapClamp);
	}

	switch (p)
	{
		case TechniqueForward:
		{
			if	(gpu_config.can_resolve_msaa)
			{
				t_color_aa = NewTexture("t_color_aa");
				t_color_aa->Create(NULL, dimensions.x, dimensions.y, t_format, aa, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
				t_depth_aa = NewTexture("t_depth_aa");
				t_depth_aa->Create(NULL, dimensions.x, dimensions.y, Render::Texture::FormatDepth, aa, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));

				buffer_fbo->SetColorTexture(t_color_aa);
				buffer_fbo->SetDepthTexture(t_depth_aa);
			}

			if	(gpu_config.use_rtt)
			{
				t_depth = NewTexture("t_depth");
				t_depth->Create(NULL, dimensions.x, dimensions.y, Render::Texture::FormatDepth, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));

				resolve_fbo->SetColorTexture(t_compose[0]);
				resolve_fbo->SetDepthTexture(t_depth);
			}
		}
		break;

		case TechniqueDeferred:
		{
			for	(uint n = 0; n < 4; ++n)
			{
				t_gbuffer[n] = NewTexture(String("t_gbuffer") << n);
				t_gbuffer[n]->Create(NULL, dimensions.x, dimensions.y, Render::Texture::FormatRGBAF, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
				t_gbuffer[n]->SetFiltering(Render::TextureParm::FilterNearest);
			}
			t_depth = NewTexture("t_depth");
			t_depth->Create(NULL, dimensions.x, dimensions.y, Render::Texture::FormatDepth, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));

			Render::Texture *rt[4] = { t_gbuffer[0], t_gbuffer[1], t_gbuffer[2], t_gbuffer[3] };

			buffer_fbo->SetColorTexture(rt, 4);
			buffer_fbo->SetDepthTexture(t_depth);

			resolve_fbo->SetColorTexture(t_compose[0]);
			resolve_fbo->SetDepthTexture(t_depth);
		}
		break;
	}

	SetPostProcess();
}
void				Renderer::SetPostProcess(uint k)
{
	if	(!gpu_config.use_rtt)
		return;

	if	(k == 0)
		k = registry.GetInteger("PostProcess:FX:Scale", 4);

	for	(uint n = 0; n < 3; ++n)
	{
		t_fx[n] = NewTexture(String("t_fx") << n);
		t_fx[n]->Create(NULL, dimensions.x / k, dimensions.y / k, Render::Texture::FormatRGBAF, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
		t_fx[n]->SetFiltering(Render::TextureParm::FilterBilinear);
		t_fx[n]->SetWrapping(Render::TextureParm::WrapClamp, Render::TextureParm::WrapClamp);
	}

	t_fx_depth = NewTexture("t_fx_depth");
	t_fx_depth->Create(NULL, dimensions.x / k, dimensions.y / k, Render::Texture::FormatDepth, Render::Texture::NoAA, Render::Texture::Usage(Render::Texture::IsRenderTarget | Render::Texture::IsShaderResource));
	t_fx_depth->SetFiltering(Render::TextureParm::FilterNearest);
	t_fx_depth->SetWrapping(Render::TextureParm::WrapClamp, Render::TextureParm::WrapClamp);

	fx_scale = k;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::SetupCoreResources(bool support_3d)
{
	// Core shaders & objects.
	if	(!LoadCoreShaders(support_3d))
		return false;

	resolve_fbo = NewFBO();
	resolve_fbo->Create();

	if	(support_3d)
	{
		buffer_fbo = NewFBO();
		buffer_fbo->Create();
		fx_fbo = NewFBO();
		fx_fbo->Create();

		// Shadow mapping objects.
		if	(gpu_config.enable_shadow)
		{
			shadow_map_fbo = NewFBO();
			shadow_map_fbo->Create();
			CreateShadowMaps();
		}

		// Default technique.
		SetRenderTechnique();

		if	(!CreateTerrainPatch())
			return false;

		t_noise = core_resource_factory->LoadTexture("@core/noise.tga");
	}

	{
		static const ushort idx[] = { 2, 1, 0, 3, 2, 0 };
		static const float vtx[] = { -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1 };

		skybox_idx_vbo = NewVBO();
		skybox_idx_vbo->Create(idx, sizeof(ushort) * 6, VBO::Index);
		skybox_vtx_vbo = NewVBO();
		skybox_vtx_vbo->Create(vtx, sizeof(float) * 3 * 4, VBO::Vertex);

		helper_idx_vbo = NewVBO();
		helper_idx_vbo->Create(idx, sizeof(ushort) * 6, VBO::Index);
		helper_vtx_vbo = NewVBO();
		helper_vtx_vbo->Create(sizeof(float) * 256, VBO::Vertex, VBO::Dynamic);	// 1Kb WARNING watch out for possible overflow when updating this buffer!

		ushort box_idx[] =
		{
			0, 1, 2,	0, 2, 3,	1, 5, 6,	1, 6, 2,
			5, 4, 7,	5, 7, 6,	4, 0, 3,	4, 3, 7,
			4, 5, 1,	4, 1, 0,	3, 2, 6,	3, 6, 7
		};
		box_idx_vbo = NewVBO();
		box_idx_vbo->Create(box_idx, sizeof(ushort) * 3 * 4 * 3, VBO::Index);

		direct_idx_vbo = NewVBO();
		direct_vtx_vbo = NewVBO();
	}
	return true;
}
void				Renderer::SetDefaultStates()
{
	// Set defaults states.
	EnableDepthTest(true);
	SetDepthFunc(DepthLess);
	EnableCulling(true);
	SetCullFunc(CullFront);
	EnableBlending(false);
	SetBlendFunc(BlendSrcAlpha, BlendOneMinusSrcAlpha);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::DiscoverGPUConfiguration()
{
	Variant v;
	if	(QueryCaps(CanBlitRBO, v))
		gpu_config.can_resolve_msaa = v.b_value;
	if	(QueryCaps(MaxAnisotropy, v))
		gpu_config.max_anisotropy = v.i_value;
	if	(QueryCaps(TextureTopLeftOrigin, v))
		gpu_config.tex_origin_is_top_left = v.b_value;

#if	__PLATFORM_ANDROID_NDK__ || __PLATFORM_IOS__ || __PLATFORM_EMSCRIPTEN__
	// Drop all advanced features... for now.
	gpu_config.enable_shadow = false;

	gpu_config.can_resolve_msaa = false;
	gpu_config.use_rtt = false;

	gpu_config.npot = RendererConfig::NPOT_Limited;
#endif

	if	(!gpu_config.can_resolve_msaa)
		__LOG_W__ << "Insufficient RBO support, falling back to RTT: No hardware MSAA.\n";
	if	(!gpu_config.enable_shadow)
		__LOG_W__ << "Shadow-mapping support disabled.\n";

	if	(gpu_config.npot == RendererConfig::NPOT_Limited)
		__LOG_V__ << "NPOT limited support.\n";
	if	(gpu_config.npot == RendererConfig::NPOT_Full)
		__LOG_V__ << "NPOT full support.\n";
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::ResizeVideo(uint w, uint h)
{
	__LOG_V__ << "GPU Resize video to " << w << "x" << h << ".\n";

	dimensions.Set(w, h);
	SetRenderTechnique(render_technique);
	return true;
}
bool				Renderer::Open(uint w, uint h, char bpp, GS::Render::VideoMode mode, const void *sys_handle)
{
	if	(!OpenPlatformVideo(w, h, bpp, mode, sys_handle))
		return false;
	if	(!InitializePlatform())
		return false;

	__LOG__ << "\n";
	__LOG_H__ << "GPU-based (" << GetName() << ") on adapter " << stats.adapter << " (vendor: " << stats.vendor << ").\n";
	__LOG__ << "\n";

	DiscoverGPUConfiguration();

	dimensions.Set(w, h);
	SetViewport(fRect(0, 0, (float)w, (float)h));
	return true;
}
void				Renderer::Free()
{
	__LOG_FUNC__

	terrain_patch_cache.Free();

	FreeRenderTechnique();

	helper_idx_vbo = NULL;
	helper_vtx_vbo = NULL;

	skybox_idx_vbo = NULL;
	skybox_vtx_vbo = NULL;

	direct_idx_vbo = NULL;
	direct_vtx_vbo = NULL;

	box_idx_vbo = NULL;

	buffer_fbo = NULL;
	resolve_fbo = NULL;
	fx_fbo = NULL;

	for	(uint n = 0; n < 2; ++n)
		t_compose[n] = NULL;

	for	(uint n = 0; n < 3; ++n)
		t_fx[n] = NULL;
	t_fx_depth = NULL;
	fx_scale = 0;

	t_noise = NULL;

	FreeShadowMaps();
	shadow_map_fbo = NULL;

	UnloadCoreShaders();
}
void				Renderer::Close()
{
	Free();
	ClosePlatformVideo();
}
//------------------------------------------------------------------------------
