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


		#include "gpu/gpu_display_list.h"
		#include "gpu/gpu_renderer.h"
		#include "core/triangle_list.h"
		#include "log/log.h"

		#define __USE_VBO__ 1

		using namespace GS::GPU;

namespace GS {
	namespace Core {
		bool ComputeVertexArrayMinMax(const Array <Vector4> &, MinMax &, const Matrix4 * = 0);
	}
}

//------------------------------------------------------------------------------
bool			DisplayList::Create(GS::Core::Trilist *trilist, GS::Render::Material *list_material)
{
	// Setup skin data.
	bone.Clone(trilist->bone);

	// Setup triangle list indices.
	index_count = trilist->idx.GetCount();

#if	__USE_VBO__

	if	(!idx)
		idx = renderer.NewVBO();
	if	(!idx || !idx->Create(index_count * sizeof(ushort), VBO::Index, VBO::Static))
		return false;

#endif

	if	(!idx_map.Allocate(index_count))
		return false;

	for	(size_t n = 0; n <index_count; ++n)
		idx_map[(int)n] = (ushort)trilist->idx[(int)n];

#if	__USE_VBO__

	idx->Update(idx_map, 0, idx_map.GetSize());
	idx_map.Free();

#endif

	// Setup triangle list vertex.
	stride = 0;

	// Vertex offset.
	vertex_offset = stride;
	stride += 3 * sizeof(hfloat);

	// Compute normal stream size.
	if	(trilist->nrm)
	{
		normal_offset = stride;
		stride += 4 * sizeof(char);
	}

	// Compute RGB stream size.
	if	(trilist->rgb)
	{
		rgb_offset = stride;
		stride += 4 * sizeof(char);
	}

	// Compute UV stream size.
	for	(int s = 0; s < __UV_PER_GEOMETRY__; ++s)
		if	(trilist->uv[s])
		{
			uv_offset[s] = stride;
			stride += 2 * sizeof(hfloat);
		}

	// Compute tangent stream size.
	if	(trilist->tangent)
	{
		tangent_offset = stride;
		stride += 4 * 2 * sizeof(char);
	}

	// Compute skinning stream size.
	if	(trilist->skin)
	{
		skinning_offset = stride;
		stride += 4 * 2 * sizeof(uchar);
	}

	#define	__GPU_PADSIZE	4

	// Compute vertex padding.
	int padding = 0;
	padding = stride % __GPU_PADSIZE ? __GPU_PADSIZE - (stride % __GPU_PADSIZE) : 0;
	if	(padding)
		__LOG__ << "Padding GPU vertex to " << __GPU_PADSIZE << "B by " << padding << "B (from " << (uint)stride << "B)\n";
	stride += padding;

	// Setup attribute streams.
	size_t vtx_stream_size = stride * size_t(trilist->vtx.GetCount());

#if	__USE_VBO__

	if	(!vtx)
		vtx = renderer.NewVBO();
	if	(!vtx || !vtx->Create(vtx_stream_size, VBO::Vertex, VBO::Static))
		return false;

#endif

	if	(!vtx_map.Allocate(vtx_stream_size))
		return false;

	char *p_stream = (char *)vtx_map.c_ptr();
	for	(uint n = 0; n < trilist->vtx.GetCount(); ++n)
	{
		// Output vertex stream.
		hfloat *p_vtx = (hfloat *)p_stream;
		p_vtx[0] = Types::FloatToHFloat(trilist->vtx[n].x);
		p_vtx[1] = Types::FloatToHFloat(trilist->vtx[n].y);
		p_vtx[2] = Types::FloatToHFloat(trilist->vtx[n].z);
		p_stream += 3 * sizeof(hfloat);

		// Output normal stream.
		if	(trilist->nrm)
		{
			schar *p_nrm = (schar *)p_stream;
			p_nrm[0] = (schar)(trilist->nrm[n].x * 127.f);
			p_nrm[1] = (schar)(trilist->nrm[n].y * 127.f);
			p_nrm[2] = (schar)(trilist->nrm[n].z * 127.f);
			p_stream += 4 * sizeof(schar);
		}

		// Output RGB stream.
		if	(trilist->rgb)
		{
			uchar *p_rgb = (uchar *)p_stream;
			p_rgb[0] = uchar(trilist->rgb[n].x * 255.f);
			p_rgb[1] = uchar(trilist->rgb[n].y * 255.f);
			p_rgb[2] = uchar(trilist->rgb[n].z * 255.f);
			p_rgb[3] = uchar(trilist->rgb[n].w * 255.f);
			p_stream += 4 * sizeof(uchar);
		}

		// Output UV streams.
		for	(uint s = 0; s < __UV_PER_GEOMETRY__; ++s)
			if	(trilist->uv[s])
			{
				hfloat *p_uv = (hfloat *)p_stream;
				p_uv[0] = Types::FloatToHFloat(trilist->uv[s][n].x);
				p_uv[1] = Types::FloatToHFloat(trilist->uv[s][n].y);
				p_stream += 2 * sizeof(hfloat);
			}

		// Output tangent stream.
		if	(trilist->tangent)
		{
			schar *p_tng = (schar *)p_stream;
			p_tng[0] = schar(trilist->tangent[n].T.x * 127.f);
			p_tng[1] = schar(trilist->tangent[n].T.y * 127.f);
			p_tng[2] = schar(trilist->tangent[n].T.z * 127.f);
			// Mind the gap!
			p_tng[4] = schar(trilist->tangent[n].B.x * 127.f);
			p_tng[5] = schar(trilist->tangent[n].B.y * 127.f);
			p_tng[6] = schar(trilist->tangent[n].B.z * 127.f);
			p_stream += 4 * 2 * sizeof(schar);
		}

		// Output skinning stream.
		if	(trilist->skin)
		{
			uchar *p_skn = (uchar *)p_stream;
			p_skn[0] = uchar(trilist->skin[n].bone_index[0]);
			p_skn[1] = uchar(trilist->skin[n].bone_index[1]);
			p_skn[2] = uchar(trilist->skin[n].bone_index[2]);
			p_skn[3] = uchar(trilist->skin[n].bone_index[3]);
			p_skn[4] = uchar(trilist->skin[n].w[0] * 255.f);
			p_skn[5] = uchar(trilist->skin[n].w[1] * 255.f);
			p_skn[6] = uchar(trilist->skin[n].w[2] * 255.f);
			p_skn[7] = uchar(trilist->skin[n].w[3] * 255.f);
			p_stream += 4 * 2 * sizeof(uchar);
		}

		p_stream += padding;
	}

#if	__USE_VBO__

	vtx->Update(vtx_map, 0, vtx_map.GetSize());
	vtx_map.Free();

#else

	p_stream = (char *)vtx_map.c_ptr();

	#define	OffsetToAdress(_Offset) \
	if (_Offset != -1) _Offset += (size_t)p_stream;

	OffsetToAdress(vertex_offset)
	OffsetToAdress(normal_offset)
	OffsetToAdress(rgb_offset)
	for	(uint n = 0; n < __UV_PER_GEOMETRY__; ++n)
		OffsetToAdress(uv_offset[n])
	OffsetToAdress(tangent_offset)
	OffsetToAdress(skinning_offset)

#endif

	Core::ComputeVertexArrayMinMax(trilist->vtx, minmax);
	material = list_material;

	return true;
}
//------------------------------------------------------------------------------
