#version 150 //GL 3.2

#include <hostdefs>
#line 4
#include "data/shader/ubo.glsl"
#line 6

#define PI       3.14159265358979323846
#define TWO_PI   6.2831853071795864769
#define PI_RECIP 0.31830988618379067153

#if NSPP == 1u
	uniform sampler2D gbuf_diff   ;
	uniform sampler2D gbuf_spec   ;
	uniform sampler2D gbuf_shin_bl;
	uniform sampler2D gbuf_emiss  ;
	uniform sampler2D gbuf_normal ;
	uniform sampler2D gbuf_depth  ;
#else
	uniform sampler2DMS gbuf_diff   ;
	uniform sampler2DMS gbuf_spec   ;
	uniform sampler2DMS gbuf_shin_bl;
	uniform sampler2DMS gbuf_emiss  ;
	uniform sampler2DMS gbuf_normal ;
	uniform sampler2DMS gbuf_depth  ;
#endif

#if   SHADMODE == SHADMODE_SHAD_VOL
	uniform sampler2D buf_light;
#elif SHADMODE == SHADMODE_SMAP
	uniform sampler2DShadow shadowmap;
#endif

const float light_inten      = 30.0;
const vec3  light_intencolor = light_inten * normalize(vec3( 1.00, 0.98, 0.96 ));



out vec4 frag_color;



//PCG32 RNG
uint _rng_state;
float rand_1f()
{
	uint old_state = _rng_state;
	_rng_state = _rng_state*747796405u + 2891336453u;

	uint rshift = old_state >> 28;
	uint rand_val = old_state ^ ( old_state >> (4u+rshift) );
	rand_val *= 277803737u;
	rand_val = rand_val ^ ( rand_val >> 22 );

	return float(rand_val) * (1.0/4294967295.0);
}
vec2 rand_2f() { return vec2( rand_1f(), rand_1f() ); }
vec3 rand_3f() { return vec3( rand_1f(), rand_1f(), rand_1f() ); }
vec4 rand_4f() { return vec4( rand_1f(), rand_1f(), rand_1f(), rand_1f() ); }

vec2 rand_disk()
{
	float r = sqrt( rand_1f() );
	float angle = rand_1f() * TWO_PI;
	return r * vec2( cos(angle), sin(angle) );
}



#if   SHADMODE == SHADMODE_SHAD_VOL
#elif SHADMODE == SHADMODE_SMAP

float shadowmap_1(
	sampler2DShadow shadowmap, vec3 pos_lgt_vp,
	float bias/*=-0.001*/
) {
	return texture( shadowmap, vec3( pos_lgt_vp.xy, pos_lgt_vp.z+bias ) );
}

float shadowmap_pcf_ndisk(
	sampler2DShadow shadowmap, vec3 pos_lgt_vp,
	float kernel_radius_texels, int kernel_sample_count,
	float bias/*=-0.001*/
) {
	float radius = kernel_radius_texels * shadowmap_res_recip;
	float sample_sum = 0.0;
	vec3 coord = vec3( pos_lgt_vp.xy, pos_lgt_vp.z+bias );
	for ( int k=0; k<kernel_sample_count; ++k )
	{
		vec3 pos_sample = vec3( coord.xy+radius*rand_disk(), coord.z );
		sample_sum += texture( shadowmap, pos_sample );
	}
	return sample_sum / kernel_sample_count;
}
float shadowmap_pcf_2x2(
	sampler2DShadow shadowmap, vec3 pos_lgt_vp,
	float bias/*=-0.001*/
) {
	float offset = 0.5 * shadowmap_res_recip;
	vec3 coord = vec3( pos_lgt_vp.xy, pos_lgt_vp.z+bias );
	return 0.25 * (
		texture( shadowmap, vec3( coord.xy+vec2(-offset,-offset), coord.z ) ) +
		texture( shadowmap, vec3( coord.xy+vec2( offset,-offset), coord.z ) ) +
		texture( shadowmap, vec3( coord.xy+vec2(-offset, offset), coord.z ) ) +
		texture( shadowmap, vec3( coord.xy+vec2( offset, offset), coord.z ) )
	);
}
float shadowmap_pcf_nxn(
	sampler2DShadow shadowmap, vec3 pos_lgt_vp, int n,
	float bias/*=-0.001*/
) {
	float avg = 0.0;
	vec3 coord = vec3( pos_lgt_vp.xy-0.5*float(n-1)*shadowmap_res_recip, pos_lgt_vp.z+bias );
	for ( int j=0; j<n; ++j )
	for ( int i=0; i<n; ++i )
	{
		avg += texture( shadowmap, vec3( coord.xy+shadowmap_res_recip*vec2(i,j), coord.z ) );
	}
	avg /= float( n * n );
	return avg;
}

/*
float texel_size_from_world_size( float world_size, float texture_distance,float texture_res )
{
	return world_size / (texture_distance*2.0*tan(to_radians(0.5*LIGHT_FOV))) * texture_res;
}
float _est_blocker_avg_depth(
	sampler2DShadow shadowmap,
	vec2 coord_x, float depth_x,
	float kernel_radius, int kernel_num_samples, float bias=0.0
) {
	float radius = kernel_radius * shadowmap_res_recip;

	float depth_total = 0.0;
	int num_blockers = 0;
	for (int i=0;i<kernel_num_samples;++i) {
		vec3 radius_and_disk = rand_disk();
		vec2 coord_sample = coord_x + radius*radius_and_disk.x*radius_and_disk.yz;
		if (coord_sample.x<=0.0 || coord_sample.x>=1.0 || coord_sample.y<=0.0 || coord_sample.y>=1.0) continue; //outside depth texture; not a blocker

		float sample_depth = texture(shadowmap,coord_sample).r;
		if (sample_depth<1.0 && sample_depth+bias<depth_x) {
			depth_total += sample_depth;
			++num_blockers;
		}
	}

	if (num_blockers==0) return -1.0;
	return depth_total / num_blockers;
}
float shadowmap_pcss(
	sampler2DShadow shadowmap, vec3 pos_light_viewport,
	float kernel_radius_texels, int kernel_sample_count,
	float bias=0.0
) {
	//User-defined parameter.  Shadowmap bias is computed from this, scaled appropriately by resolution.
	//	For a 256x256 texture, the value works out to be exactly this.
	#define BIAS_FACTOR 0.005

	//Heuristic based on blocker having half the z value of the receiver (note nonlinear), clamped
	//	to a maximum 1/8th the shadowmap size.
	float blocker_search_radius = min(
		texel_size_from_world_size( 0.5*light_radius, 0.5*pos_light_viewport.z, res_largest ),
		res_largest*0.125
	);
	float blocker_depth = _est_blocker_avg_depth(
		shadowmap,res_largest,
		testcoord.xy,depth_x,
		blocker_search_radius,16,BIAS_FACTOR*256.0/res_largest
	);
	if (blocker_depth==-1.0) blocker_depth=depth_x; //No blockers found; same as the object itself being the blocker

	float penumbra_width = (depth_x-blocker_depth)/depth_x * light_radius; //in world
	float filter_radius = texel_size_from_world_size( penumbra_width, blocker_depth,res_largest ); //in texels
	//filter_radius = max(filter_radius,15.0);\n" //This is necessary because thin blockers might not be found.
	float shadow = pcf(depthmaps[0],res_largest, testcoord.xy,depth_x, filter_radius,32,BIAS_FACTOR*256.0/res_largest);

	shadow_term = vec3(shadow);
}*/

float shadowmap_lookup( vec3 pos_wld )
{
	vec4 pos_light_clip = matr_PV_light * vec4( pos_wld, 1.0 );
	if ( pos_light_clip.w <= 0.0 ) return 1.0; //Behind light frustum

	vec3 pos_light_ndc = pos_light_clip.xyz / pos_light_clip.w;
	if (any(greaterThanEqual( abs(pos_light_ndc.xy), vec2(1.0) ))) return 1.0; //Off to side

	vec3 pos_light_viewport = 0.5*pos_light_ndc + vec3(0.5);

	//return shadowmap_1( shadowmap, pos_light_viewport, -0.001 );
	//return shadowmap_pcf_2x2( shadowmap, pos_light_viewport, 0.001 );
	return shadowmap_pcf_nxn( shadowmap, pos_light_viewport, SMAP_PCF_RT, -0.001 );
	//return shadowmap_pcf_ndisk( shadowmap, pos_light_viewport, 4.0,8 );
}

#endif



vec3 unproject( vec2 pixel,float z_screen, vec4 viewport )
{
	vec3 vert_screen = vec3( pixel, z_screen );
	vec4 vert_ndc = vec4(
		vec3( (2.0*vert_screen.xy-viewport.xy)/viewport.zw, 2.0*vert_screen.z ) - vec3(1.0),
		1.0
	);
	vec4 vert_wld = matr_PV_inv * vert_ndc;
	vert_wld /= vert_wld.w;
	return vert_wld.xyz;
}

ivec2 g_pixel_coord;

vec4 shade_sample(
	vec3 mtl_diff, vec3 mtl_spec, vec2 mtl_shin_bl, vec3 mtl_emiss,
	vec3 normal,
	float depth
) {
	vec3 pos_wld = unproject( gl_FragCoord.xy,depth, viewport );

	#if   SHADMODE == SHADMODE_SHAD_VOL
		float light_vis = texelFetch( buf_light, g_pixel_coord, 0 ).r;
	#elif SHADMODE == SHADMODE_SMAP
		float light_vis = shadowmap_lookup(pos_wld);
	#endif
	light_vis = max( light_vis, shadow_darkness );

	vec3 L = normalize( light_pos_xyzw .xyz - pos_wld );
	vec3 V = normalize( camera_pos_xyzw.xyz - pos_wld );
	vec3 N = normalize(normal); //Because unpacked from lossy texture
	float geom_term = max( dot(N,L), shadow_darkness );

	float lambert_sc = PI_RECIP;
	vec3 lambert = mtl_diff * lambert_sc;

	float shin = mtl_shin_bl.x;
	//Note original Phong, not modified Phong (basically, considers geometry term premultiplied)
	float phong_sc = (TWO_PI/(shin+1.0)) * pow( max(dot( reflect(-V,N), L ),0.0), shin );
	vec3 phong = mtl_spec * phong_sc;

	vec3 L_refl = light_intencolor*mix( lambert   *geom_term,phong   , mtl_shin_bl.y );
	L_refl *= light_vis;

	vec3 L_ret = L_refl + mtl_emiss;

	return vec4( L_ret, 1.0 );
	//return vec4( vec3(light_vis), 1.0 );

	//return vec4( color, 1.0 );
	//return vec4( N, 1.0 );
	//return vec4( gl_FragCoord.xy/viewport.zw, depth, 1.0 );
}

void main()
{
	g_pixel_coord = ivec2(gl_FragCoord.xy);
	_rng_state = uint(g_pixel_coord.y)*1103515245u + uint(g_pixel_coord.x)*12345u;

	#if NSPP == 1u

		vec3  mtl_diff    = texelFetch( gbuf_diff   , g_pixel_coord, 0 ).rgb;
		vec3  mtl_spec    = texelFetch( gbuf_spec   , g_pixel_coord, 0 ).rgb;
		vec2  mtl_shin_bl = texelFetch( gbuf_shin_bl, g_pixel_coord, 0 ).xy ;
		vec3  mtl_emiss   = texelFetch( gbuf_emiss  , g_pixel_coord, 0 ).rgb;
		vec3  normal      = texelFetch( gbuf_normal , g_pixel_coord, 0 ).xyz;
		float depth       = texelFetch( gbuf_depth  , g_pixel_coord, 0 ).r  ;

		frag_color = shade_sample( mtl_diff,mtl_spec,mtl_shin_bl,mtl_emiss, normal, depth );

	#else

		vec4 shaded = vec4(0.0);
		for ( int k=0; k<int(NSPP); ++k )
		{
			vec3  mtl_diff    = texelFetch( gbuf_diff   , g_pixel_coord, k ).rgb;
			vec3  mtl_spec    = texelFetch( gbuf_spec   , g_pixel_coord, k ).rgb;
			vec2  mtl_shin_bl = texelFetch( gbuf_shin_bl, g_pixel_coord, k ).xy ;
			vec3  mtl_emiss   = texelFetch( gbuf_emiss  , g_pixel_coord, k ).rgb;
			vec3  normal      = texelFetch( gbuf_normal , g_pixel_coord, k ).xyz;
			float depth       = texelFetch( gbuf_depth  , g_pixel_coord, k ).r  ;

			shaded += shade_sample( mtl_diff,mtl_spec,mtl_shin_bl,mtl_emiss, normal, depth );
		}
		shaded *= 1.0/NSPP;

		frag_color = shaded;

	#endif
}
