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


		#include <cstddef>
		#include "gpu/gpu_renderer.h"
		#include "gpu/gpu_geometry.h"
		#include "core/renderer_environment_interface.h"
		#include "core/terrain.h"
		#include "core/geometry.h"
		#include "sort/sort.h"
		#include "container/container_sort.h"
		#include "platform_config.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GPU;


//------------------------------------------------------------------------------
void				Renderer::DrawList(const Stack <RenderPrimitive *> &dl_list, const DrawContext &dc)
{
	ResetDisplayListCache();
	DecayTerrainCache();

	m_previous_iview = view_item->GetPreviousMatrix().InversedFast();

	for (uint n = 0; n < dl_list.GetCount(); ++n)
	{
		RenderPrimitive *dl = dl_list[n];

		if	(!dl->item)
			continue;

		switch (dl->type)
		{
			case RenderPrimitive::TypeDisplayList:
				if	(dl->dlst)
				{
					m_previous_world = dl->item->GetPreviousMatrix();
					SetWorldMatrix(dl->item->GetMatrix(), &dl->item->GetInverseMatrix());
					DrawDisplayListCached(*dl->dlst, *dl->item, dc);
				}
				break;

			case RenderPrimitive::TypeTerrainPatch:
				if	(dl->patch)
					RenderTerrainPatch(*dl->patch, *dl->item, dc);
				break;

			case RenderPrimitive::TypeEmitter:
				break;
		}
	}

	ResetDisplayListCache();
	stats.queue_pass++;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static inline uint	GetRenderPrimitiveSortKey(RenderPrimitive *a)
{
	// Watch nRenderMaterial and nGPUDisplayList size in bytes so that this key stays optimal.
	return ((((uint)a->dlst->material.c_ptr() >> 8) & 0xffff) << 16) + (((uint)a->dlst >> 7) & 0xffff);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::SortDisplayList(AutoStack <RenderPrimitive *> &d_list) const
{
	const uint count = d_list.GetCount();
	if	(count == 0)
		return;
	try {
		// Byte sort list.
		typedef Sort<uint, RenderPrimitive *> SortPrimitive;
		Array <SortPrimitive::Entry> sort_a(count), sort_b(count);

		for (uint n = 0; n < count; ++n)
		{
			sort_a[n].o = d_list[n];
			sort_a[n].v = GetRenderPrimitiveSortKey(d_list[n]);
		}
		Array <SortPrimitive::Entry> *out = SortPrimitive::ByteSort(count, &sort_a, &sort_b);

		// Empty the source stack, prevent primitives cleanup...
		d_list.DropContentOwnership();

		// ...as we now insert them back in sorted order.
		for (uint n = 0; n < count; ++n)
			d_list.Push((*out)[n].o);
	}
	catch(char *e)
	{
		__LOG__ << "Failed to SortDisplayList.\n";
	}
}
static int			GetMaterialDrawPassIndex(Render::Material *m, float opacity = 1.f)
{
	if	(m->renderword & Core::Material::Render_UseFramebuffer)
		return 2;
	if	((m->blendop != Core::Material::Blend_None) || (opacity < 1.0))
		return 1;
	return 0;
}
void				Renderer::BuildDisplayLists(const AutoStack <Render::Primitive *> &p_list, AutoStack <RenderPrimitive *> *d_list) const
{
	for (uint n = 0; n < p_list.GetCount(); ++n)
	{
		Render::Primitive *p = p_list[n];

		switch (p->type)
		{
			case Render::Primitive::Type_Geometry:
				if	(Geometry *geo = (Geometry *)p->geometry.c_ptr())
					for	(uint n = 0; n < geo->display_list.GetCount(); ++n)
					{
						DisplayList *dlist = geo->display_list[n];
						if	(dlist->material.IsNull())
							continue;

						int draw_pass_index = GetMaterialDrawPassIndex(dlist->material, p->item->opacity);
						d_list[draw_pass_index].Push(new RenderPrimitive(p->item, dlist));
					}
				break;

			case Render::Primitive::Type_TerrainPatch:
				if	(p->patch->terrain->render_data->material.IsNull())
					continue;
				d_list[GetMaterialDrawPassIndex(p->patch->terrain->render_data->material, p->patch->terrain->opacity)].Push(new RenderPrimitive(p->item, p->patch));
				break;

			case Render::Primitive::Type_Emitter:
				break;
		}
	}

	for	(int n = 0; n < 3; ++n)
		SortDisplayList(d_list[n]);
}
void				Renderer::RenderList()
{
	ScopedBenchmark bench(stats.bench_render);
	PerfSetMarker("RenderList", Color::Green);

	// Build primitive list.
	stats.renderable_processed += BuildRenderablePrimitiveList(*view_item, *view_item, frustum_rlist.primitive_list);
	stats.renderable_drawn += frustum_rlist.primitive_list.GetCount();

	// Build display lists.
	BuildDisplayLists(frustum_rlist.primitive_list, frustum_rlist.display_lists);

	// Render opaque display lists.
	switch (render_technique)
	{
		case TechniqueDeferred:
			RenderListDeferred(frustum_rlist.display_lists[0]);
			break;

		case TechniqueForward:
			RenderListForward(frustum_rlist.display_lists[0], DrawContext::Opaque);

			if	(environment_interface)	// draw skybox
			{
				Render::Shader *user_skybox_shader = environment_interface->GetSkyboxShader();

				Render::sTexture skybox_layers[2];
				if	(environment_interface->GetSkyboxLayers(skybox_layers) || user_skybox_shader)
					DrawSkybox(skybox_layers, user_skybox_shader);
			}
			break;
	}

	// Render transparent display lists.
	RenderListForward(frustum_rlist.display_lists[1], DrawContext::Alpha);

	// Render frame buffer dependent display lists.
	if	(gpu_config.use_rtt && ((frustum_rlist.display_lists[2].GetCount() > 0) || registry.GetBool("FrameBufferAsTexture;", false)))
	{
		GrabDisplay(t_fx[0]);
		RenderListForward(frustum_rlist.display_lists[2], DrawContext::Opaque);
	}

	// Render direct user primitives hook.
	if	(environment_interface)
		environment_interface->OnRenderUser(this);

	//
	switch (render_technique)
	{
		case TechniqueDeferred:
			// Render fog.
			if	(environment_interface)
				if	(environment_interface->IsFogEnabled() && !performance_tools.disable_fog)
				{
					EnableBlending(true);
					RenderFullscreenQuad(*ds_fog_program, viewport.GetWidth() / (float)dimensions.x, viewport.GetHeight() / (float)dimensions.y);
					EnableBlending(false);
				}
			break;

		case TechniqueForward:
			// Resolve MSSA target.
			if	(gpu_config.enable_aa)
				render_fbo->Blit(resolve_fbo, iRect(0, 0, dimensions.x, dimensions.y), iRect(0, 0, dimensions.x, dimensions.y), true, true);
			break;
	}

	// Post-processes.
	if	(gpu_config.use_rtt)
	{
		t_final = ApplyPostProcessChain(frustum_rlist.display_lists);
		resolve_fbo->SetColorTexture(t_final);
	}

	// Drop render list.
	frustum_rlist.Clear(false);

	// Subsequent draws should render to the final texture.
	if	(gpu_config.use_rtt)
		SetCurrentFBO(resolve_fbo);
	render_fbo = resolve_fbo;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Renderer::BeginDrawList()
{
	PerfSetMarker("BeginDrawList", Color::Green);

	// Cache per-frame values for coherency.
	frame_clock = environment_interface ? environment_interface->GetClock() : 0;
	pcf_radius = registry.GetReal("ShadowMapping:PCF:Radius", 1.75f);

	if	(environment_interface)
		if	(Core::Camera *c = environment_interface->GetCurrentCamera())
		{
			SetCamera(c);
			ApplyCamera();
		}

	/*
		MSAA needs a special resolve step so we work on a different FBO which
		will later be resolved to the resolve FBO.
	*/
	render_fbo = gpu_config.enable_aa ? buffer_fbo : resolve_fbo;

	if	(render_technique == TechniqueForward)
	{
		if	(gpu_config.use_rtt)
			SetCurrentFBO(render_fbo);

		// Clear output, if a skybox is set it will be rendered after the opaque pass.
		if	(environment_interface)
		{
			Render::Shader *user_skybox_shader = environment_interface->GetSkyboxShader();

			Render::sTexture skybox_layers[2];
			if	(environment_interface->GetSkyboxLayers(skybox_layers) || user_skybox_shader)
				Clear(0, 0, 0, 1, 1, ClearDepth);
			else
			{
				Color bg = environment_interface->GetClearColor();
				Clear(bg.x, bg.y, bg.z);
			}
		}
	}

	t_final = t_compose[0];
	return true;
}
void				Renderer::EndDrawList()
{
	PerfSetMarker("EndDrawList", Color::Green);

	// when entering this function the current FBO is expected to be render_FBO.
	if	(gpu_config.use_rtt)
	{
		// Blit result to user output_fbo.
		if	(output_fbo)
			render_fbo->Blit(output_fbo, viewport.AsInt(), output_fbo_rect, true, false);

		// Blit to frame buffer.
		SetCurrentFBO(NULL);
		fRect src(viewport.sx / dimensions.x, viewport.sy / dimensions.y, viewport.ex / dimensions.x, viewport.ey / dimensions.y);
		RenderFullscreenQuad(*single_texture_program, src, fRect(0, 0, 1, 1), t_final);	// viewport is already set for destination so use the full rect output

		// Restore default output.
		resolve_fbo->SetColorTexture(t_final = t_compose[0]);
	}

	SetIndexSource(NULL);
	SetVertexSource(NULL, 0);
}
//------------------------------------------------------------------------------
