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


		#include "gpu/gpu_renderer.h"
		#include "gpu/gpu_geometry.h"
		#include "gpu/gpu_shader_object.h"
		#include "gpu/gpu_draw_context.h"
		#include "core/object.h"
		#include "core/camera.h"
		#include "core/geometry.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::GPU;
		

//------------------------------------------------------------------------------
#define	ENABLE_PERFORMANCE_TOOLS

#ifdef	ENABLE_PERFORMANCE_TOOLS
	#define	_ProgramCacheMiss		++gpu_stats.prg_change;
	#define	_DisplayListCacheMiss	++gpu_stats.dls_change;
	#define	_MaterialCacheMiss		++gpu_stats.mat_change;
	#define	_ItemCacheMiss			++gpu_stats.item_change;

	#define	_SetPerfWire			if	(performance_tools.show_wireframe)	SetFillMode(FillWireframe);
	#define	_UnsetPerfWire			if	(performance_tools.show_wireframe)	SetFillMode(FillSolid);
#else
	#define	_ProgramCacheMiss
	#define	_DisplayListCacheMiss
	#define	_MaterialCacheMiss
	#define	_ItemCacheMiss

	#define	_SetPerfWire
	#define	_UnsetPerfWire
#endif
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Renderer::ResetDisplayListCache()
{
	if	(dls_cache.dls)
		SetDisplayList(NULL);
	dls_cache.dls = NULL;

	if	(dls_cache.mat)
		UnsetMaterial(*dls_cache.mat, dls_cache.ctx);
	dls_cache.mat = NULL;

	if	(dls_cache.shd)
		SetShaderProgram(NULL, dls_cache.shd);
	dls_cache.shd = NULL;

	dls_cache.item = NULL;
}
bool				Renderer::SetDrawContext(DisplayList &dls, const Core::Item &item, const DrawContext &dc)
{
	Material *mat = dls.material.IsValid() ? (Material *)dls.material.c_ptr() : NULL;
	if	(!mat || !mat->shader)
		return false;
	Shader *shd = mat->shader->variants[dc.ctx.variant];
	if	(!shd)
		return false;

	#define	ProgramChange		(1 << 0)
	#define	DisplayListChange	(1 << 1)
	#define	MaterialChange		(1 << 2)
	#define	ItemChange			(1 << 3)
	#define	OpacityChange		(1 << 4)

	uint change_mask = 0;
	change_mask |= (shd != dls_cache.shd) ? ProgramChange : 0;
	change_mask |= (&dls != dls_cache.dls) ? DisplayListChange : 0;
	change_mask |= (mat != dls_cache.mat) ? MaterialChange : 0;
	change_mask |= (&item != dls_cache.item) ? ItemChange : 0;
	change_mask |= (item.opacity != dls_cache.opacity) ? OpacityChange : 0;

	if	(change_mask & ProgramChange)
	{
		if	(!SetShaderProgram(shd, dls_cache.shd))
			return false;
		_ProgramCacheMiss
	}

	if	(change_mask & ProgramChange)
		shd->SetConstantInputs();

	if	(change_mask & DisplayListChange)
	{
		SetDisplayList(&dls);
		_DisplayListCacheMiss
	}
	if	(change_mask & MaterialChange)
	{
		if	(dls_cache.mat)
			UnsetMaterial(*dls_cache.mat, dc.ctx);
		SetMaterial(*mat, dc.ctx, asbool(item.opacity < 1.f));
		_MaterialCacheMiss
	}

	// Program inputs.
	if	(change_mask & ProgramChange)
	{
		shd->SetRendererInputs(*this, mat);
		if	(dc.light)
			shd->SetLightInputs(*this, *view_item, *dc.light);
	}
	if	(change_mask & (ProgramChange | MaterialChange | OpacityChange))
		shd->SetMaterialOpacityInputs(*mat, item.opacity);
	if	(change_mask & (ProgramChange | MaterialChange))
	{
		shd->SetMaterialInputs(*mat);
		shd->SetTextureInputs();
	}
	if	(change_mask & (ProgramChange | DisplayListChange))
		shd->SetVertexStreamInputs(dls);
	if	(change_mask & (ProgramChange | ItemChange))
	{
		shd->SetTransformInputs(m_projection, m_view, m_iview, &m_world, &m_iworld);
		shd->SetPreviousTransformInputs(m_projection, m_previous_iview, &m_previous_world);
		_ItemCacheMiss
	}
	if	(change_mask & (ProgramChange | DisplayListChange | ItemChange))
		if	(Core::Skin *skin = ((Core::Object &)item).GetSkin())
			shd->SetSkinInputs(dls, *skin);

	shd->CommitInputs();

	// Sync cache.
	dls_cache.ctx = dc.ctx;
	dls_cache.shd = shd;
	dls_cache.mat = mat;
	dls_cache.dls = &dls;
	dls_cache.item = &item;
	dls_cache.opacity = item.opacity;

	return true;
}
void				Renderer::DrawDisplayListCached(DisplayList &dls, const Core::Item &item, const DrawContext &dc)
{
	if	(!SetDrawContext(dls, item, dc))
		return;

	// Draw.
	_SetPerfWire
	dls.Draw();
	_UnsetPerfWire

	stats.list_drawn++;
	stats.triangle_drawn += dls.index_count / 3;
}
//------------------------------------------------------------------------------
