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


#ifndef	__GPURENDERER__
#define	__GPURENDERER__


		#include "gpu/gpu_shader_object.h"
		#include "gpu/gpu_fbo.h"
		#include "gpu/gpu_vbo.h"
		#include "gpu/gpu_render_list.h"
		#include "gpu/gpu_draw_context.h"
		#include "gpu/gpu_config.h"
		#include "gpu/gpu_shader_compiler.h"
		#include "gpu/gpu_perf_tools.h"
		#include "gpu/gpu_terrain.h"
		#include "gpu/gpu_caps.h"
		#include "core/render_resource_factory.h"
		#include "core/renderer.h"
		#include "core/camera.h"
		#include "memory/nweak_ptr.h"


namespace GS {
	namespace GPU {
		class PrepareLightJob;

/*!
	@short	GPU based renderer class.
	@author Emmanuel Julien (ejulien@gsworks.fr)
*/
class	Renderer : public Render::Renderer
{
		AutoPtr <Render::ResourceFactory> core_resource_factory;

public:

/*!
	@name	Factories.
	@{
*/
virtual	FBO				*NewFBO() = 0;
virtual	VBO				*NewVBO() = 0;
virtual	DisplayList		*NewDisplayList() = 0;
virtual	ShaderObject	*NewGPUShaderObject() = 0;
/*!
	@}
	@name	Render path.
	@{
*/
		bool			pending_shadow_map_refresh,
						pending_core_shader_refresh,
						pending_technique_refresh,
						pending_fx_refresh;

		RendererConfig	gpu_config;
		PerformanceTools performance_tools;

		/// Process registry message.
virtual	RegistryRValue	ProcessMessage(RegistryMessage message, const Registry *, const void *parm);

		/// Query a caps on the renderer.
virtual	bool			QueryCaps(Caps, Variant &) { return false; }
		/// Discover and apply the GPU configuration.
virtual	void			DiscoverGPUConfiguration();
/// @}

enum RenderTechnique
{
	TechniqueDefault	=	0,
	TechniqueForward,			///< Forward rendering
	TechniqueDeferred			///< Deferred rendering for opaque polygons, forward for alpha polygons.
};

		RenderTechnique	render_technique;

		/// Return the default render technique.
		RenderTechnique	GetDefaultRenderTechnique() const;

		/// Refresh render technique.
		void			RefreshRenderTechnique();
		/// Free render technique objects.
		void			FreeRenderTechnique();
		/// Switch to a specific render technique.
		void			SetRenderTechnique(RenderTechnique = TechniqueDefault);

		/// Setup post-process system.
		void			SetPostProcess(uint k = 0);

/*!
	@name	Statistics.
	@{
*/
		RendererStatistics gpu_stats;

		/// Draw the renderer profiler output.
virtual	void			DrawProfilerText(Render::RasterFont *font[2], float &x, float &y);
		/// Reset statistics.
virtual	void			ResetStatistics()
		{
			gpu_stats.Reset();
			stats.Reset();
		}
/// @}

/*!
	@name	State functions.
	@{
*/
enum GPUFillMode
{
	FillSolid		=	0,
	FillWireframe,
	FillLast
};

		/// Set primitive fill mode.
virtual	void			SetFillMode(GPUFillMode) = 0;

enum GPUCullFunc
{
	CullFront		=	0,
	CullBack,
	CullLast
};

		/// Set culling function.
virtual	void			SetCullFunc(GPUCullFunc) = 0;
		/// Enable culling.
virtual	void			EnableCulling(bool) = 0;

enum	GPUDepthFunc
{
	DepthNever		=	0,
	DepthLess,
	DepthEqual,
	DepthLessEqual,
	DepthGreater,
	DepthNotEqual,
	DepthGreaterEqual,
	DepthAlways,
	DepthFuncLast
};

		/// Set depth function.
virtual	void			SetDepthFunc(GPUDepthFunc) = 0;
		/// Set depth read.
virtual	void			EnableDepthTest(bool) = 0;
		/// Set depth write.
virtual	void			EnableDepthWrite(bool) = 0;

enum	BlendFunc
{
	BlendZero		=	0,
	BlendOne,
	BlendSrcAlpha,
	BlendOneMinusSrcAlpha,
	BlendDstAlpha,
	BlendOneMinusDstAlpha,
	BlendLast
};

		/// Set blend function.
virtual	void			SetBlendFunc(BlendFunc src, BlendFunc dst) = 0;
		/// Enable blend.
virtual	void			EnableBlending(bool) = 0;

		/// Enable alpha to coverage.
virtual	void			EnableAlphaToCoverage(bool) = 0;
		/// Enable alpha testing.
virtual	void			EnableAlphaTest(bool, float threshold = 0.f) = 0;

		/// Set vertex buffer object.
virtual	void			SetVertexSource(VBO *, size_t stride) = 0;
		/// Set index buffer object.
virtual	void			SetIndexSource(VBO *) = 0;

		/// Set shader object.
virtual	bool			SetShaderProgram(Shader *, Shader *unset = 0) = 0;

		/// Set display list.
virtual	void			SetDisplayList(DisplayList *);

protected:

		Matrix4			m_projection,
						m_view, m_iview,
						m_world, m_iworld;

		Matrix4			m_previous_iview,		// Motion blur specific.
						m_previous_world;
/// @}

		SharedPtr <Shader>
						single_texture_color_program,
						single_texture_program,
						single_color_program,
						simple_program,

						tone_mapping_program,
						single_texture_cutoff_program,
						single_texture_fx_program,
						shader_program,

						fx_blur_program,
						noise_program,
						sharpen_program,
						hsl_program,
						chromatic_dispersion_program,

						ssaa_program,
						ssao_program,
						ssao_blur_program,

						radial_blur_program,
						motion_blur_program,

						skybox_program;

/*!
	@name	Terrain system.
	@{
*/
		Array <char> terrain_patch_vtx;
		Array <CachedTerrainPatch> terrain_patch_cache;

		/// Ensure lower entries will get out of cache.
		void			DecayTerrainCache();
		/// Upload a terrain patch to a cache page.
		void			UploadTerrainPatchToPage(CachedTerrainPatch &page, const Core::Item &item, const Core::Patch &patch, const DrawContext &dc);
		/// Render a terrain patch.
		void			RenderTerrainPatch(const Core::Patch &, const Core::Item &, const DrawContext &);

public:

		/// Set default states.
		void			SetDefaultStates();
		/// Setup core resources.
		bool			SetupCoreResources(bool support_3d = true);

		/// Invalidate the terrain cache.
		void			InvalidateTerrainCache(int u = 0, int v = 0, int w = 0, int h = 0);
		/// Create the static terrain patch.
		bool			CreateTerrainPatch();
/// @}

/*!
	@name	Core shader.
	@{
*/
protected:

		NML::File		shader_dict,
						material_dict;

		/// Marshall core shader source.
		void			MarshallCoreShader(String &source);

		/// Load a shader from a resource file.
		Shader			*SetupCoreShader(const char *name);

		bool			LoadCoreShaders(bool support_3d = true);
		void			UnloadCoreShaders();

public:

		AutoPtr <IShaderCompiler> shader_compiler;

		List <MaterialShader *> material_shaders;

		List <Render::Texture *> textures;
		List <Render::Shader *> shaders;

		NML::Tag		*GetPCFShaderTag() const;
		NML::Tag		*GetPSMShaderTag() const;
		NML::Tag		*GetPSSMShaderTag() const;

		const NML::File	&GetShaderDictionary() const
		{	return shader_dict;	}
		const NML::File	&GetMaterialDict() const
		{	return material_dict;	}

		uint			fx_scale;
		FBO				*render_fbo;

		bool			normal_depth_updated;

		AutoPtr <FBO>	buffer_fbo, fx_fbo;
		Render::sTexture t_compose[2], t_depth;		///< 1:1
		Render::sTexture t_color_aa, t_depth_aa;	///< 1:1
		Render::sTexture t_fx[3], t_fx_depth;		///< Downscaled FX render targets.

		AutoPtr <FBO> resolve_fbo;					///< For MSAA support.

		FBO				*output_fbo;
		iRect			output_fbo_rect;

		AutoPtr <FBO>	shadow_map_fbo;
		Render::sTexture shadow_map;

		float			pcf_radius;
		float			frame_clock;

		Render::sTexture t_noise;
/// @}

/*!
	@name	Post-processes.
	@{
*/
		Render::Texture	*t_final;

		/// Apply directional blur to a texture.
		void			ApplyDirectionalBlur(Render::Texture *t[2], const Vector2 &d, float attn = 0.975f);

		void			ApplyBloomFilter(Render::Texture *t, float strength, float threshold, float exponent, float radius, float strength_white_screen);
		bool			ApplyNoiseFilter(Render::Texture *t_in, Render::Texture *t_out, float strength, float monochromatic, float bias);
		bool			ApplySharpenFilter(Render::Texture *t_in, Render::Texture *t_out, float strength);
		bool			ApplyHSL(Render::Texture *t_in, Render::Texture *t_out, float H, float S, float L);
		bool			ApplyChromaticDispersion(Render::Texture *t_in, Render::Texture *t_out, float width);
		bool			ApplySSAAFilter(Render::Texture *t_in, Render::Texture *t_out);
		bool			ApplySSAOFilter(Render::Texture *t_in, Render::Texture *t_out, const Stack <RenderPrimitive *> [2], float strength, float radius, float clip_distance, float blur_radius);
		bool			ApplyRadialBlur(Render::Texture *t_in, Render::Texture *t_out, float strength, float center_x, float center_y);
		bool			ApplyMotionBlur(Render::Texture *t_in, Render::Texture *t_out, const Stack <RenderPrimitive *> [2], float strength, int quality = 1);

		void			GetPostProcessNormalDepth(const Stack <RenderPrimitive *> display_lists[2]);

		/// Apply post-processes.
		Render::Texture	*ApplyPostProcessChain(const Stack <RenderPrimitive *> [2]);
/// @}

/*!
	@name	Deferred shading.
	@{
*/

		Render::sTexture t_gbuffer[4];

protected:

struct	ViewConfig
{
	Core::Camera *camera;
	fRect		viewport;

	ViewConfig(Core::Camera *c, const fRect &v) : camera(c), viewport(v) {}
};

		fRect			viewport;
		fRect			clipping;

		/// Backup the current view configuration.
		ViewConfig		BackupView() const;
		/// Restore a view configuration.
		void			RestoreView(const ViewConfig &);
		/// Restore a view configuration viewport.
		void			RestoreViewport(const ViewConfig &);

		/// Allocate shadow map.
		void			CreateShadowMaps();
		/// Free shadow map.
		void			FreeShadowMaps();

		AutoPtr <Shader> ambient_program,
						pointlight_program,
						pointlight_shadow_program,
						spotlight_program,
						spotlight_shadow_program,
						linearlight_program,
						linearlight_shadow_program;

		AutoPtr <Shader> ds_fog_program;

		/// Render fullscreen light volume.
		void			RenderFullscreenLight(Shader &, Core::Light &);
		/// Render point light volume.
		void			RenderPointLight(Core::Light &, const DrawContext &);
		/// Render spot light volume.
		void			RenderSpotLight(Core::Light &, const DrawContext &);
		/// Render linear light volume.
		void			RenderLinearLight(Core::Light &, const DrawContext &);

public:

struct	ShadowMapSplit
{
	Core::Camera	view;
	RenderableList	list;
	fRect			rect;
};

		bool			LightPreparePSM(const Core::Light &, bool build_dlist) const;
		bool			LightPreparePSSM(const Core::Light &, bool build_dlist) const;
		bool			LightPrepareSSM(const Core::Light &, bool build_dlist) const;

		/// Prepare shadow map for a light.
		bool			PrepareShadowMap(const Core::Light &, bool build_dlist) const;

		void			LightRenderPSM(Core::Light &);
		void			LightRenderSSM(Core::Light &);
		void			LightRenderPSSM(Core::Light &);

		/// Render shadow map for a light.
		void			RenderShadowMap(Core::Light &);
/*!
	@}
	@name	Deferred render pass.
	@{
*/
protected:

		/// Render G-buffer map.
		void			RenderGBufferPass(const Stack <RenderPrimitive *> &);
		/// Render light accumulation map.
		void			RenderDeferredLightPass(const Stack <RenderPrimitive *> &);

		/// Render primitive list using deferred shading.
		void			RenderListDeferred(const Stack <RenderPrimitive *> &);
/// @}

/*!
	@}
	@name	Forward render pass.
	@{
*/
		void			SetupForwardContext(const DrawContext::Context &);

		/// Cull display lists against a given light volume.
		void			LightCullDisplayList(const Core::Light &, const Stack <RenderPrimitive *> &in, Stack <RenderPrimitive *> &out);
		/// Render primitive list using forward rendering.
		void			RenderListForward(Stack <RenderPrimitive *> &, DrawContext::Render);

		void			DrawForwardLightContribution(DrawContext::Render, const List <Core::Light *> &, const AutoStack <PrepareLightJob *> &, const Array <Stack <RenderPrimitive *> > &);
		void			DrawForwardBaseConstant(DrawContext::Render, const Stack <RenderPrimitive *> &);
/// @}

/*!
	@name	Common render pass.
	@{
*/
		RenderableList	frustum_rlist, shadow_rlist;

struct	DisplayListCache
{
	DrawContext::Context	ctx;

	DisplayList		*dls;
	Material		*mat;
	Shader			*shd;

	const Core::Item *item;
	float			opacity;

	DisplayListCache() : dls(0), mat(0), shd(0), item(0), opacity(-1.f) {}
};

		DisplayListCache dls_cache;

		/// Reset the display list cache.
		void			ResetDisplayListCache();
		/// Set draw context.
		bool			SetDrawContext(DisplayList &, const Core::Item &, const DrawContext &);
		/// Draw a display list using the current renderer cache state.
		void			DrawDisplayListCached(DisplayList &, const Core::Item &, const DrawContext &);

		/// Set direct primitive draw state.
		void			DrawSetState(Core::Material::BlendOperator bo, Core::Material::RenderWord f);
		/// Restore direct primitive draw state.
		void			DrawRestoreState(Core::Material::BlendOperator bo, Core::Material::RenderWord f);

		/// Render skybox.
		void			DrawSkybox(Render::sTexture [2], Render::Shader * = 0);

public:

		/// Draw primitive list using a specific program.
		void			DrawList(const Stack <RenderPrimitive *> &, const DrawContext &);
		/// Build display lists.
		void			BuildDisplayLists(const AutoStack <Render::Primitive *> &, AutoStack <RenderPrimitive *> *) const;
		/// Sort display list per render attributes.
		void			SortDisplayList(AutoStack <RenderPrimitive *> &) const;
/// @}

/*!
	@name	Helper functions.
	@{
*/
		AutoPtr <VBO>	helper_idx_vbo, helper_vtx_vbo;
		AutoPtr <VBO>	skybox_idx_vbo, skybox_vtx_vbo;

		AutoPtr <VBO>	direct_idx_vbo, direct_vtx_vbo;

		AutoPtr <VBO>	box_idx_vbo;	// for deferred light volume drawing

		/// Render a fullscreen quad.
		void			RenderFullscreenQuad(Shader &, float k_x, float k_y, Render::Texture * = 0, Core::Light * = 0);
		void			RenderFullscreenQuad(Shader &, const fRect &nUnused(src_rect), const fRect &nUnused(dst_rect), Render::Texture * = 0, Core::Light * = 0);

		/// Translate a shader from ISL to the target platform language.
virtual	bool			TranslateShader(const Core::Shader &, String &, String &) const = 0;

		/// Set output FBO object (the current frame buffer is blit over this FBO color attachment in EndDrawList).
		void			SetOutputFBO(FBO *fbo = 0, const iRect *rect = 0)
		{
			output_fbo = fbo;
			if	(rect)
				output_fbo_rect = *rect;
		}

		int				GetAnisotropySampleCount(Render::TextureParm::Anisotropy) const;

		/// Set current FBO.
virtual	void			SetCurrentFBO(FBO * = 0) = 0;
		/// Check the current FBO state.
virtual	bool			CheckCurrentFBO() = 0;

struct	DirectVertexLayout
{
	size_t	stride;
	size_t	vtx_offset, color_offset, uv_offset;
};

		/// Helper to build a direct vertex layout from distinct input streams.
		bool			BuildDirectVertexLayout(uint idx_count, const ushort *idx, const Vector4 *v, const Color *c, const Vector2 *uv, DirectVertexLayout &, VBO *idx_vbo = 0, VBO *vtx_vbo = 0);
/// @}

/*!
	@name	Performance functions.
	@{
*/
		/// Set a single shot event.
virtual	void			PerfSetMarker(const char *name, const Color &) {}

		/// Start a performance event.
virtual	void			PerfBeginEvent(const char *name, const Color &) {}
		/// End a performance event.
virtual	void			PerfEndEvent() {}

//
struct	ScopedPerfEvent
{
		Renderer		*renderer;

		ScopedPerfEvent(Renderer *_renderer, const char *name, const Color &color) : renderer(_renderer)
		{	renderer->PerfBeginEvent(name, color);	}
		~ScopedPerfEvent()
		{	renderer->PerfEndEvent();	}
};
/// @}

/*!
	@name	Low-level draw.
	@{
*/
		/// Set material.
		void			SetMaterial(const Material &, const DrawContext::Context &, bool force_alpha = false);
		/// Unset material.
		void			UnsetMaterial(const Material &, const DrawContext::Context &);

		/// Draw elements.
virtual	void			DrawElements(Types::PrimitiveType p_type, int index_count, Types::ValueType i_type = Types::ValueLast, const void *indice = 0) = 0;
/// @}

		/// Set the view matrix.
		void			SetViewMatrix(const Matrix4 &, const Matrix4 *inverse = 0);
		/// Set the projection matrix.
		void			SetProjectionMatrix(const Matrix4 &);
		/// Set the world matrix.
		void			SetWorldMatrix(const Matrix4 &, const Matrix4 *inverse = 0);

		/// Draw a line to the frame buffer.
virtual	void			DrawLine(uint count, const Vector4 *, const Color * = 0, Core::Material::BlendOperator = Core::Material::Blend_None, Core::Material::RenderWord = Core::Material::Render_None, Render::Shader * = 0);
		/// Draw a triangle to frame buffer.
virtual	void			DrawTriangle(uint count, const Vector4 *, const ushort * = 0, const Color * = 0, const Vector2 * = 0, const Render::Texture * = 0, Core::Material::BlendOperator = Core::Material::Blend_None, Core::Material::RenderWord = Core::Material::Render_None, Render::Shader * = 0);
		/// Draw sprite.
virtual	void			DrawSprite(uint count, const Vector4 *, const Color * = 0, const float *size = 0, const Render::Texture * = 0, float global_size = 1.f, Core::Material::BlendOperator = Core::Material::Blend_None, Core::Material::RenderWord = Core::Material::Render_None, Render::Shader * = 0);

/*!
	@name	Render-to-texture and spline rendering.
	@author Nicolas RACOT
	@{
*/
		/// Create a render-target texture.
		Render::Texture	*CreateRenderTexture(const char *name, uint width, uint height, Render::Texture::Format format = Render::Texture::FormatRGBA8, Render::Texture::AA aa = Render::Texture::NoAA);
		/// Clear a texture with a solid color.
		void			ClearTexture(Render::Texture *target, const Color &clear_color = Color(0,0,0,0));
		/// Copy/blit a source texture to a destination texture.
		void			BlitTextureToTexture(Render::Texture *src, Render::Texture *dst, const fRect *src_rect = 0, const fRect *dst_rect = 0);
		/// Draw an interpolated spline on a texture with configurable width and color.
		void			DrawSplineToTexture(Render::Texture *target, const Array<Vector2> &control_points, float width_pixels, const Color &color, int samples_per_segment = 16, bool clear_target = false, bool soft_edge = true, const Color *border_color = nullptr, float border_width = 0.0f, float margin = 0.0f);

private:
		/// Helper structure for spline tessellation.
		struct SplineRibbon
		{
			Array<Vector4>	positions;
			Array<ushort>	indices;
			Array<Color>	colors;
			Array<Vector2>	uvs;  // for soft edge (distance from centerline)
		};

		/// Catmull-Rom spline interpolation.
		Vector2			CatmullRomInterpolate(const Vector2 &p0, const Vector2 &p1, const Vector2 &p2, const Vector2 &p3, float t) const;
		/// Tessellate a spline into ribbon geometry.
		void			TessellateSplineRibbon(const Array<Vector2> &control_points, float width_normalized, const Color &color, int samples_per_segment, bool soft_edge, SplineRibbon &out_ribbon);

public:
/// @}

		/// Initialize platform.
virtual	bool			InitializePlatform() = 0;
		/// Open platform video system.
virtual	bool			OpenPlatformVideo(uint w, uint h, char d, Render::VideoMode m, const void *sys_handle = 0) = 0;
		/// Close platform video system.
virtual	void			ClosePlatformVideo() = 0;

		/// Render the display list.
		void			RenderList();

		/// Output string to screen.
virtual	void			Write(const Render::RasterFont &, const char *, float &x, float &y, const WriterConfig &, float s = 1, const Color *c = 0, WriterAlignment a = AlignLeft, bool mirrored = false);

		/// Open video.
virtual	bool			Open(uint w, uint h, char d = 32, Render::VideoMode m = Render::VideoWindowed, const void *sys_handle = 0);
		/// Free all renderer resources.
virtual	void			Free();
		/// Close video.
virtual	void			Close();
		/// Explicitly resize video output.
virtual	bool			ResizeVideo(uint w, uint h);

		/// Begin a drawing operation.
		bool			BeginDrawList();
		/// End a drawing operation.
		void			EndDrawList();

		Renderer();
};

	} // GPU
} // GS


#endif	// __GPURENDERER__
