float4x4 g_matInvViewProj;
float4x4 g_matInvView;
float4x4 g_matInvProj;
float4x4 g_matView;
float2 g_RTSize;
float g_RTIntermediateSize;
float2 g_RTRequiredResSize;
float g_ReqResDownSampleSize;
float3 g_LightDir;

int g_TileCount = 32;
int g_iPass = 0;

texture g_texDiffuse;
sampler2D g_sDiffuse = sampler_state
{
    Texture = <g_texDiffuse>;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = NONE;
    MagFilter = POINT;
    MinFilter = POINT;
};

texture g_texNormal;
sampler2D g_sNormal = sampler_state
{
    Texture = <g_texNormal>;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = NONE;
    MagFilter = POINT;
    MinFilter = POINT;
};

texture g_texRequiredRes;
sampler2D g_sRequiredRes = sampler_state
{
    Texture = <g_texRequiredRes>;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = NONE;
    MagFilter = POINT;
    MinFilter = POINT;
};

texture g_texTileResIntermediate;
sampler2D g_sTileResIntermediate = sampler_state
{
    Texture = <g_texTileResIntermediate>;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = NONE;
    MagFilter = POINT;
    MinFilter = POINT;
};

texture g_texID;
sampler2D g_sID = sampler_state
{
    Texture = <g_texID>;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = NONE;
    MagFilter = POINT;
    MinFilter = POINT;
};

#include "ShadowMapping.fx"
#include "WPT.fx"

struct SHADOW_OUT
{
	float4 Shadow : COLOR0;
	float Z : DEPTH;
};

float g_BiasScale = 1;

float4 PSSMTile(float2 ScreenCoord:TEXCOORD0, uniform int wpt, uniform bool id, uniform bool filter) : COLOR
{
	SHADOW_OUT o;

	float3 wpos = GetFragmentWorldPos(ScreenCoord, wpt);
	
	float2 NXY = tex2D(g_sNormal, ScreenCoord);
	float3 Normal = float3(NXY, sqrt(1 - NXY.x*NXY.x - NXY.y*NXY.y));
	float4 Diffuse = tex2D(g_sDiffuse, ScreenCoord);
	Normal.z *= Diffuse.a * 2 - 1;

	float slope = (1-saturate(dot(Normal, -g_LightDir)));
    float bias = slope * SM_SlopeBias + SM_DetphBias;
	bias *= g_BiasScale;
	//bias *= (1 + (vcam.z/2000)*20);
	float4 swpos = float4(wpos + bias * -g_LightDir, 1);
	//float4 swpos = wpos;

	float2 ShadowDepth = CalcDepth(swpos);
	float SDepth = ShadowDepth.x/ShadowDepth.y;
	float4 SMTexC = CalcSMTexC(swpos);

	float2 tc = SMTexC.xy/SMTexC.w;

	float2 rsdepthproj = CalcDepth(float4(wpos, 1));
	float rsdepth = rsdepthproj.x / rsdepthproj.y;

	{
		//ShadowDepth is from biased world so its not exactly 1 and 0
		float2 ShadowDepthUnbiased = CalcDepth(float4(wpos, 1));
		float shdepthunbiased = ShadowDepthUnbiased.x/ShadowDepthUnbiased.y;
		if(shdepthunbiased <= 0.001 || shdepthunbiased >= 0.999)
			clip(-1);

		//clip if outside shadow map
		float4 vclip = float4(tc - 0.5/SM_ShadowMapSize, 1 - tc - 0.5/SM_ShadowMapSize);
		clip(vclip);
	}
	
	float2 samplepos = frac(tc * SM_ShadowMapSize);
	float conf = 1 - max(abs(samplepos.x - 0.5f), abs(samplepos.y - 0.5f))*2;

	//if smslope == 1, definitely in shadow
	if(slope >= 1)
	{
		return float4(1, g_iPass / 64.0f, 1, 0);
	}
	else
	{
		float sh;

		if (filter)
		{
			//sh = CalcShadow_4xPCF(SMTexC, SDepth);
			sh = CalcShadow_9xGaussPCF(SMTexC.xy / SMTexC.w, SDepth);			
		}
		else 
		{
			sh = CalcShadow_Simple(SMTexC, SDepth, id ? tex2D(g_sID, ScreenCoord).r : 0);
		}

		return float4(sh, g_iPass / 64.0f, conf, 0);
	}	
}

//calculates the required resolution and tile index for each fragment
float4 PSRequiredRes(float2 ScreenCoord:TEXCOORD0, uniform int wpt) : COLOR
{
	if (1 && wpt == WPT_DEPTH) //do reconstruction of fragment with surface normal, so far works only with projected depth
	{
		//calculate width/height of fragment in worldspace
		
		float projz = tex2D(g_sDepth, ScreenCoord).r;
		float projw = g_matInvProj[2].w * projz + g_matInvProj[3].w;
		
		//float2 fxy = float2(g_matInvProj[0].x, g_matInvProj[1].y) / projw;
		//float2 dxy = 2 * fxy / g_RTSize;
		//because we guarantee a projection matrix that generates square fragments we only need to calculate one dimension
		
		float fxy = g_matInvProj[0].x / projw;
		float dxyhalf = fxy / g_RTSize.x; //width/height of fragment seen in viewspace
		
		//return dxyhalf;
		
		float2 vxy = mul(ScreenCoord * float2(2, -2) - float2(1, -1) + float2(-1, 1) / g_RTSize, g_matInvProj);
		
		float3 vspace = float3(vxy, 1) / projw;
				
		float2 nxy = tex2D(g_sNormal, ScreenCoord);
		float3 normal = float3(nxy, sqrt(1 - nxy.x*nxy.x - nxy.y*nxy.y));
		//normal.z *= tex2D(g_sDiffuse, ScreenCoord).a * 2 - 1; // normal orientation does not make any difference here
		
		float3 normal_vspace = mul(normal, g_matView);
		float3 viewvec = normalize(vspace);
		
		float dotvn = dot(normal_vspace, -viewvec);
		//float dotln = dot(normal, -g_LightDir);
		
		float4x4 invViewSMTex = mul(g_matInvView, GetMatSMTex());
		
		float4 smpos = mul(float4(vspace, 1), invViewSMTex);
		float2 tc_center = smpos.xy / smpos.w;
		
		float2 reqres = 0;
		
		if(abs(dotvn) > 0.1)
		//[branch] if(dotvn > 0.1 && dotln > 0.05)
		{
			//plane of surface in viewspace
			//float4 psurf = float4(normal_vspace, -dot(vspace, normal_vspace));
			
			//float3 viewvec0 = normalize(float3(mul(ScreenCoord * float2(2, -2) - float2(1, -1) + (float2(-1, 1) + float2(-0.5, -0.5)) / g_RTSize, g_matInvProj).xy, 1));
			//float3 viewvec1 = normalize(float3(mul(ScreenCoord * float2(2, -2) - float2(1, -1) + (float2(-1, 1) + float2(0.5, -0.5)) / g_RTSize, g_matInvProj).xy, 1));
			//float3 viewvec2 = normalize(float3(mul(ScreenCoord * float2(2, -2) - float2(1, -1) + (float2(-1, 1) + float2(-0.5, 0.5)) / g_RTSize, g_matInvProj).xy, 1));
			//float3 viewvec3 = normalize(float3(mul(ScreenCoord * float2(2, -2) - float2(1, -1) + (float2(-1, 1) + float2(0.5, 0.5)) / g_RTSize, g_matInvProj).xy, 1));
			
			float3 viewvec0 = (vspace + float3(-dxyhalf, -dxyhalf, 0));
			float3 viewvec1 = (vspace + float3(dxyhalf, -dxyhalf, 0));
			float3 viewvec2 = (vspace + float3(-dxyhalf, dxyhalf, 0));
			float3 viewvec3 = (vspace + float3(dxyhalf, dxyhalf, 0));
			
			//return vspace.xyzz/500;
			
			float plane_d_neg = dot(vspace, normal_vspace);
			float3 p0 = plane_d_neg / dot(viewvec0, normal_vspace) * viewvec0;
			float3 p1 = plane_d_neg / dot(viewvec1, normal_vspace) * viewvec1;
			float3 p2 = plane_d_neg / dot(viewvec2, normal_vspace) * viewvec2;
			float3 p3 = plane_d_neg / dot(viewvec3, normal_vspace) * viewvec3;
			
		
			//assume uniform viewvector over fragment
			
			//float3 viewvec = normalize(vspace);
			//float3 viewnormal = mul(normal, g_matView);
			
			//float2 dz = dxyhalf / (1 - viewnormal.xy);
			
			//return viewnormal.xyzz;
			
			//float4 v0 = float4(vspace.x - dxyhalf, vspace.y - dxyhalf, vspace.z - dz.x - dz.y, 1);
			//float4 v1 = float4(vspace.x + dxyhalf, vspace.y - dxyhalf, vspace.z + dz.x - dz.y, 1);
			//float4 v2 = float4(vspace.x - dxyhalf, vspace.y + dxyhalf, vspace.z - dz.x + dz.y, 1);
			//float4 v3 = float4(vspace.x + dxyhalf, vspace.y + dxyhalf, vspace.z + dz.x + dz.y, 1);
			
			//float4 v0 = float4(float3(vspace.x - dxyhalf, vspace.y - dxyhalf, vspace.z) + viewvec * (- dz.x - dz.y), 1);
			//float4 v1 = float4(float3(vspace.x + dxyhalf, vspace.y - dxyhalf, vspace.z) + viewvec * (+ dz.x - dz.y), 1);
			//float4 v2 = float4(float3(vspace.x - dxyhalf, vspace.y + dxyhalf, vspace.z) + viewvec * (- dz.x + dz.y), 1);
			//float4 v3 = float4(float3(vspace.x + dxyhalf, vspace.y + dxyhalf, vspace.z) + viewvec * (+ dz.x + dz.y), 1);

			//v0 = mul(v0, g_matInvView);
			//v1 = mul(v1, g_matInvView);
			//v2 = mul(v2, g_matInvView);
			//v3 = mul(v3, g_matInvView);
			
			
					
			float4 v0 = mul(float4(p0, 1), invViewSMTex);
			float4 v1 = mul(float4(p1, 1), invViewSMTex);
			float4 v2 = mul(float4(p2, 1), invViewSMTex);
			float4 v3 = mul(float4(p3, 1), invViewSMTex);
			
			//v0 = CalcSMTexC(v0);
			//v1 = CalcSMTexC(v1);
			//v2 = CalcSMTexC(v2);
			//v3 = CalcSMTexC(v3);
			
			//return float4(abs(GetFragmentWorldPos(ScreenCoord + float2(1/g_RTSize.x, 0), wpt) - GetFragmentWorldPos(ScreenCoord, wpt)), 1);
			//return float4(abs(v0.xyz - GetFragmentWorldPos(ScreenCoord, wpt)) / 30, 1);
			
			float2 tc0 = v0.xy / v0.w;
			float2 tc1 = v1.xy / v1.w;
			float2 tc2 = v2.xy / v2.w;
			float2 tc3 = v3.xy / v3.w;
			
			clip(float4(tc0, 1-tc0)); //dont accumulate if v0 is outside sm
			
			float2 minxy = min(tc0, min(tc1, min(tc2, tc3)));
			float2 maxxy = max(tc0, max(tc1, max(tc2, tc3)));

			float2 tcdxy = maxxy - minxy;

			float2 pixeldist = tcdxy * SM_ShadowMapSize;
			float2 xres = 1 / pixeldist;

			reqres = round(log2(xres * SM_ShadowMapSize) + 0.5f) / 255;
		
		}
		
		return float4(round(tc_center * 256 - 0.5f) / 255, reqres); //tccenter == xy index of tile, will be discretized to 256 tiles
		
		//float3 wposexact = GetFragmentWorldPos(ScreenCoord, wpt);
		//float4 wpos2 = mul(float4(vspace, 1), g_matInvView);
		//return float4(wpos2.xyz/wpos2.w / 2000, 1);
		//return float4(abs(wposexact.xyz - wpos2.xyz/wpos2.w) / 50, 1);
	}
	else //do reconstruction through neighbouring fragments
	{
		//return float4(abs(GetFragmentWorldPos(ScreenCoord + float2(1/g_RTSize.x, 0), wpt) - GetFragmentWorldPos(ScreenCoord, wpt)), 1);
	
		float2 rcpRTSize = 1.0f / g_RTSize;

		float4 smtc;
					
		smtc = CalcSMTexC(float4(GetFragmentWorldPos(ScreenCoord, wpt), 1));
		float2 tccenter = smtc.xy / smtc.w;

		//[branch]if(tccenter < 0 ||tccenter > 1)
		//	return 0;
		clip(float4(tccenter, 1-tccenter)); //dont accumulate if outside sm

		smtc = CalcSMTexC(float4(GetFragmentWorldPos(ScreenCoord + float2(rcpRTSize.x, 0), wpt), 1));
		float2 tc_x = smtc.xy / smtc.w;
		
		smtc = CalcSMTexC(float4(GetFragmentWorldPos(ScreenCoord + float2(0, rcpRTSize.y), wpt), 1));
		float2 tc_y = smtc.xy / smtc.w;		

		smtc = CalcSMTexC(float4(GetFragmentWorldPos(ScreenCoord + float2(rcpRTSize.x, rcpRTSize.y), wpt), 1));
		float2 tc_xy = smtc.xy / smtc.w;
		
		float2 minxy = min(tccenter, min(tc_x, min(tc_y, tc_xy)));
		float2 maxxy = max(tccenter, max(tc_x, max(tc_y, tc_xy)));

		float2 tcdxy = maxxy - minxy;

		float2 pixeldist = tcdxy * SM_ShadowMapSize;
		float2 xres = 1 / pixeldist;

		float2 res = round(log2(xres * SM_ShadowMapSize) + 0.5f) / 255;
		
		return float4(round(tccenter * 256 - 0.5f) / 255, res); //tccenter == xy index of tile, will be discretized to 256 tiles
		//return float4(round(tccenter*256 - 0.5f) / 255, round(tccenter*256 - 0.5f) / 255); 
		//return float4(res, 0, 1);
	}
}

void VSTileRes(float2 TexC : POSITION, out float4 oPos : POSITION, out float oRes : TEXCOORD0, uniform bool writex)
{
	float4 tileres = tex2Dlod(g_sRequiredRes, float4((TexC + 0.5) / g_RTRequiredResSize, 0, 0));

	//if (tileres.x == 128 / 255.f || tileres.y == 128 / 255.f)
	//	tileres.zw = 1;

	//tileres.xy = float2(1, 254) / 255.f;

	float2 pos = tileres.xy - 0.5f / g_RTIntermediateSize;

	if ((writex ? tileres.z : tileres.w) == 0) //transform them to hell if res = 0
		pos = -666;

	//oPos = float4(pos * float2(2, -2) - float2(1, -1) + float2(-4, 4) / g_RTIntermediateSize, 0, 1);
	oPos = float4(pos.xy * float2(2, -2) - float2(1, -1), 0, 1);
	oRes = writex ? tileres.z : tileres.w;
}

SHADOW_OUT PSTileRes(float iRes : TEXCOORD0)
{
	//clip(iRes - 1/255); //dont render if res = 0;

	SHADOW_OUT o;
	o.Z = iRes;
	o.Shadow = iRes * 255; // shift to integer
	return o;
}

float4 PSReqResDownSample(float2 RTCoord : TEXCOORD0) : COLOR
{
	int2 xysamples = g_RTSize / g_ReqResDownSampleSize;
	
	int2 pixelindex = (RTCoord - 0.5f / g_ReqResDownSampleSize) * g_RTSize;

	float2 reqressum = 0;
	int samplecnt = 0;
	float2 centerblock = -1;
	float2 lastprocblock = -1;

	[loop] for (int y = 0; y < xysamples.y; y++)
	{
		[loop] for(int x = 0; x < xysamples.x; x++)
		{
			float2 samplecoord = ((pixelindex + float2(x, y)) + 0.5f) / g_RTSize;
			float4 sample = tex2Dlod(g_sRequiredRes, float4(samplecoord, 0, 0));
			if (sample.z > 0 || sample.w > 0) //sum only process if reqres > 0
			{
				lastprocblock = sample.xy;
				reqressum += sample.zw;
				samplecnt++;				

				if (x == xysamples.x/2 && y == xysamples.y/2)
				{
					centerblock = sample.xy;
				}
			}
		}
	}

	if (samplecnt > 0)
	{
		float2 reqresavg = round(reqressum * 255 / samplecnt) / 255.f;
		float2 blockindex = centerblock.x == -1 ? lastprocblock : centerblock;
		
		return float4(blockindex, reqresavg);
	}
	else
	{
		return 0;
	}
}

//render from intermediate to final tileres buffer
float4 PSDownSampleX4(float2 RTCoord : TEXCOORD0, uniform bool avg) : COLOR
{
	//read 4x4 block and build avg or take min	
	float ret = 0;
	int cnt = 0;
	for (int y = 0; y < 4; y++)
	{
		for (int x = 0; x < 4; x++)
		{
			float v = tex2Dlod(g_sTileResIntermediate, float4(RTCoord + (float2(x, y) - 1.5f) / g_RTIntermediateSize, 0, 0)).r;
			ret = avg ? ret + v : max(ret, v);
			if (v > 0) //only accumulate values > 0, 0 means there is no shadow in this tile
				cnt++;
		}
	}
	
	return avg && cnt > 0 ? round(ret * 255 / cnt) / 255.f : ret;
}

stateblock RSRequiredRes = stateblock_state
{
	CullMode = NONE;
    ZEnable = false;
    AlphaBlendEnable = false;
    AlphaTestEnable = false;
};

stateblock RSTileRes = stateblock_state
{
	CullMode = NONE;
	ZEnable = true;
	ZWriteEnable = true;
	ZFunc = GREATER;
	PointSize = 1;
	PointSpriteEnable = false;
	AlphaBlendEnable = false;
	AlphaTestEnable = false;
};

technique RenderTileRes_X
{
	pass P0
    {      
		StateBlock = (RSRequiredRes);

        VertexShader = compile vs_3_0 VSTileRes(true);
        PixelShader  = compile ps_3_0 PSTileRes();
    }
}

technique RenderTileRes_Y
{
	pass P0
    {      
		StateBlock = (RSRequiredRes);

        VertexShader = compile vs_3_0 VSTileRes(false);
        PixelShader  = compile ps_3_0 PSTileRes();
    }
}

technique RenderReqResDownSample
{
	pass P0
    {      
		StateBlock = (RSRequiredRes);

        VertexShader = null;
        PixelShader  = compile ps_3_0 PSReqResDownSample();
    }
}

technique RenderDownSampleX4_Max
{
	pass P0
    {      
		StateBlock = (RSRequiredRes);

        VertexShader = null;
        PixelShader  = compile ps_3_0 PSDownSampleX4(false);
    }
}

technique RenderDownSampleX4_Avg
{
	pass P0
    {      
		StateBlock = (RSRequiredRes);

        VertexShader = null;
        PixelShader  = compile ps_3_0 PSDownSampleX4(true);
    }
}

#define RenderRequiredRes(nameext, wpt)						\
technique RenderRequiredRes_##nameext						\
{															\
    pass P0													\
    {														\
		StateBlock = (RSRequiredRes);						\
															\
        VertexShader = null;								\
        PixelShader  = compile ps_3_0 PSRequiredRes(wpt);	\
    }														\
}

RenderRequiredRes(World, WPT_WORLDPOS);
RenderRequiredRes(Depth, WPT_DEPTH);
RenderRequiredRes(LinearDepth, WPT_LINEARDEPTH);

#define RenderSMTile(nameext, wpt, filter, id)				\
technique RenderSMTile_##nameext							\
{															\
    pass P0													\
    {														\
		StateBlock = (RSRequiredRes);						\
															\
        VertexShader = null;								\
        PixelShader  = compile ps_3_0 PSSMTile(wpt, id, filter);\
    }														\
}

RenderSMTile(World, WPT_WORLDPOS, false, false);
RenderSMTile(Depth, WPT_DEPTH, false, false);
RenderSMTile(LinearDepth, WPT_LINEARDEPTH, false, false);

RenderSMTile(World_Filter, WPT_WORLDPOS, true, false);
RenderSMTile(Depth_Filter, WPT_DEPTH, true, false);
RenderSMTile(LinearDepth_Filter, WPT_LINEARDEPTH, true, false);

RenderSMTile(World_ID, WPT_WORLDPOS, false, true);
RenderSMTile(Depth_ID, WPT_DEPTH, false, true);
RenderSMTile(LinearDepth_ID, WPT_LINEARDEPTH, false, true);
