/*=============================================================================
	AmbientOcclusionShader.usf - Contains shaders for screen space ambient occlusion.
	Copyright 1998-2010 Epic Games, Inc. All Rights Reserved.
=============================================================================*/

#include "Common.usf"

// The Cg preprocessor gives an error if you use a macro that hasn't been defined instead of just defaulting to 0
#ifndef AO_QUALITY
#define AO_QUALITY 0
#endif

#ifndef APPLY_FROM_AOHISTORY
#define APPLY_FROM_AOHISTORY 0
#endif

#ifndef ARBITRARY_PROJECTION
#define ARBITRARY_PROJECTION 0
#endif

#ifndef AO_ANGLEBASED
#define AO_ANGLEBASED 0
#endif

#ifndef USE_MANUAL_DEPTH_TEST
#define USE_MANUAL_DEPTH_TEST 0
#endif

/** Texture storing occlusion calculated so far in x and downsampled depth in y. */
sampler2D AmbientOcclusionTexture;

/** Texture storing fog factors. */
sampler2D FogFactorTexture;

/** Transforms a position in NDC space [-1, 1] to screen space, taking into account a half pixel offset for D3D. */
float4 AOScreenPositionScaleBias;

/** Helper functions that hide the storage semantics of occlusion and depth. */
float CalcDownsampledDepth(float2 UV)
{
	return tex2D(AmbientOcclusionTexture, UV).y;
}

float CalcOcclusion(float2 UV)
{
	return tex2D(AmbientOcclusionTexture, UV).x;
}

float2 CalcOcclusionAndDepth(float2 UV)
{
	return tex2D(AmbientOcclusionTexture, UV).xy;
}

float4 OutputOcclusionAndDepth(float Occlusion, float Depth)
{
	return float4(Occlusion, Depth, 0, 0);
}

/** Texture storing a running occlusion history in x and a convergence weight in y. */
sampler2D AOHistoryTexture;

float CalcHistoryOcclusion(float2 UV)
{
	return tex2D(AOHistoryTexture, UV).x;
}

float2 CalcHistoryOcclusionAndConvergence(float2 UV)
{
	return tex2D(AOHistoryTexture, UV).xy;
}

/*-----------------------------------------------------------------------------
	DownsampleDepth
-----------------------------------------------------------------------------*/

/** The size of half of a scene color texel. */
float2 HalfSceneColorTexelSize;

void DownsampleDepthVertexMain(
	in float4 InPosition : POSITION,
	in float2 InTexCoord : TEXCOORD0,
	out float2 OutTexCoord0 : TEXCOORD0,
	out float2 OutTexCoord1 : TEXCOORD1,
	out float2 OutTexCoord2 : TEXCOORD2,
	out float4 OutPosition : POSITION
	)
{
	OutPosition = InPosition;
	// Offsets around the actual texture coordinate to get a representation of neighbor depths
	OutTexCoord0 = InTexCoord - HalfSceneColorTexelSize;
	OutTexCoord1 = InTexCoord + float2(HalfSceneColorTexelSize.x, 0);
	OutTexCoord2 = InTexCoord + float2(0, HalfSceneColorTexelSize.y);
}

void DownsampleDepthPixelMain(
	in float2 TexCoord0 : TEXCOORD0,
	in float2 TexCoord1 : TEXCOORD1,
	in float2 TexCoord2 : TEXCOORD2,
	out float4 OutColor : COLOR0
	)
{
	float A0 = CalcSceneDepth(TexCoord0);
	float A1 = CalcSceneDepth(TexCoord1);
	float A2 = CalcSceneDepth(TexCoord2);
	float A3 = CalcSceneDepth(TexCoord1 + TexCoord2 - TexCoord0);

	OutColor = OutputOcclusionAndDepth(min(min(A0, A1), min(A2, A3)), max(max(A0, A1), max(A2, A3)));
}

struct FAOVertexOutput
{
	float2 TexCoord : TEXCOORD0;
	float3 ViewScreenVector : TEXCOORD1;
	float4 Position : POSITION;
};

/** transform from [-1,1] screen space to view */
float4x4 ScreenToView;

void OcclusionVertexMain(
	in float4 InPosition : POSITION,
	in float2 InTexCoord : TEXCOORD0,
	out FAOVertexOutput Out
	)
{
	Out.Position = InPosition;
	Out.TexCoord = InTexCoord;

	// deproject to view space
	Out.ViewScreenVector = MulMatrix(ScreenToView, float4(InPosition.xy, 1, 0)).xyz;
}

/** RGBA8 linear texture containing random normals */
sampler2D RandomNormalTexture;

/** Occlusion buffer size / RandomNormalTexture size */
float4 NoiseScale;

/** X and Y scaling factors from the world DPG projection matrix multiplied with AOScreenPositionScaleBias.xy. */
float2 ProjectionScale;

/** This frame's projection matrix */
float4x4 ProjectionMatrix;

/** OcclusionRadius, <unused>, HaloDistanceThreshold, HaloOcclusion */
float4 OcclusionCalcParameters;

/** Amount that distance should affect HaloDistanceThreshold */
float HaloDistanceScale;

/** OcclusionPower, OcclusionScale, OcclusionBias, MinOcclusion */
float4 OcclusionRemapParameters;

/** OcclusionFadeoutMinDistance, 1.0f / (OcclusionFadeoutMaxDistance - OcclusionFadeoutMinDistance) */
float4 OcclusionFadeoutParameters;

/** Transforms a fixed screen space radius into view space along the x axis when multiplied with view space z. */
float MaxRadiusTransform;

/*-----------------------------------------------------------------------------
	Occlusion
-----------------------------------------------------------------------------*/

// Nicely distributed points with distances from 1/8 to 8/8
static float3 OcclusionSamplesOffsets2[8]=
{	
/*	// perfect set of 8 points on the sphere hull (good for GA)
	float3(0.859532, 0.000000, 0.511081),
	float3(-0.859532, 0.000000, 0.511081),
	float3(0.000000, 0.859532, 0.511081),
	float3(0.000000, -0.859532, 0.511081),
	float3(0.607781, 0.607781, -0.511081),
	float3(0.607781, -0.607781, -0.511081),
	float3(-0.607781, 0.607781, -0.511081),
	float3(-0.607781, -0.607781, -0.511081),
*/
/*	// GA optimized set of points in a sphere linear distribution
	float3(0.060645, 0.023958, -0.106645),
	float3(-0.178660, -0.068360, 0.160958),
	float3(0.014047, 0.311915, 0.207694),
	float3(-0.357005, 0.298427, -0.183000),
	float3(-0.327691, -0.400003, -0.351058),
	float3(0.315235, -0.418624, -0.536545),
	float3(0.786164, 0.374348, -0.086225),
	float3(0.689650, -0.488882, 0.534208),
*/
	// GA optimized set of points in a sphere pow 0.5 distribution (points distributed over the volume) -6.939256
	float3(0.068099, -0.029749, 0.345655),
	float3(-0.333219, -0.031481, -0.371448),
	float3(0.484993, -0.106742, -0.358312),
	float3(0.140918, 0.672336, -0.167649),
	float3(0.005538, -0.785597, -0.088357),
	float3(-0.633421, 0.527250, 0.266055),
	float3(-0.744960, -0.458875, 0.330861),
	float3(0.870996, 0.392627, 0.295312),

/*
	// All on sphere hull
	float3(-0.589649, -0.571643, 0.570559),
	float3(-0.679751, -0.339607, -0.650082),
	float3(0.280437, 0.857973, 0.430392),
	float3(0.562617, -0.230637, 0.793895),
	float3(-0.061129, 0.677360, -0.733108),
	float3(-0.832599, 0.525045, 0.176373),
	float3(0.339856, -0.926040, -0.164160),
	float3(0.942497, 0.099293, -0.319123),
*/
/*
	// Distributed in the sphere pow1 = linear	
	float3(-0.056490, 0.017785, 0.110080),
	float3(0.112933, -0.035664, -0.220169),
	float3(0.327870, 0.151761, 0.100474),
	float3(0.424298, -0.232626, 0.125921),
	float3(-0.054868, 0.339186, -0.522080),
	float3(-0.580044, 0.285268, -0.380357),
	float3(0.359886, -0.672053, -0.429478),
	float3(0.885028, 0.072010, -0.459936),
*/
/*
	// Distributed in the sphere pow2	
	float3(-0.007061, 0.002224, 0.013760),
	float3(0.028235, -0.008912, -0.055042),
	float3(0.127388, 0.045102, 0.038905),
	float3(0.221260, -0.095918, 0.065907),
	float3(-0.034293, 0.211991, -0.326300),
	float3(-0.435033, 0.213951, -0.285268),
	float3(0.314901, -0.588047, -0.375793),
	float3(0.885028, 0.072010, -0.459936),
*/
};

// Nicely distributed points
static float3 OcclusionSamplesOffsets3[]=
{
	// GA optimized set of points in a sphere pow 0.5 distribution (points distributed over the volume) -17.233309
	// to be mirrored with RandomMirrorDisc.dds xy used, z dropped
	float3(0.016987, 0.353134, 0.002735),
	float3(-0.499725, 0.016565, 0.000278),
	float3(0.521023, -0.321706, 0.006338),
	float3(0.617887, 0.343620, -0.011841),
};


// @return NormAngle means 0..1 is actually 0..PI
// correct would be return acos(x) / PI;
float acosApproxNormAngle(float x)
{
	// linear approximation
	return saturate((1 - x) * 0.5f);
}

// @return like length() without sqrt()
float length2(float3 vec)
{
	return dot(vec, vec);
}


/**
 * Pixel shader that calculates an occlusion factor per-pixel using a heuristic dependent only on scene depth. 
 */
#if	!AO_ANGLEBASED
void OcclusionPixelMain(
	in FAOVertexOutput In,
	out float4 OutColor : COLOR0
	)
{
	float SceneDepth = CalcDownsampledDepth(In.TexCoord);

	// Calculate view space position of the current pixel
	float3 ViewSpacePosition = In.ViewScreenVector.xyz * SceneDepth.x;
	// Get a random normal for this pixel
	half3 RandomNormal = tex2D(RandomNormalTexture, In.TexCoord * NoiseScale.xy).xyz;

	// PS3 texture lookup already does *2-1
#if !PS3
	RandomNormal = RandomNormal*2-1;
#endif

	half3 RandomNormalX2 = RandomNormal * 2.0f;

#if AO_QUALITY == 0 || AO_QUALITY == 1
	// Clamp the radius in screen space to provide an upper bound on texture cache thrashing, which is esp needed on xenon.
	float Radius = min(MaxRadiusTransform * SceneDepth, OcclusionCalcParameters.x);
#else
	float Radius = OcclusionCalcParameters.xxx;
#endif

	float HaloDistanceThreshold = -OcclusionCalcParameters.z - HaloDistanceScale * SceneDepth;

	// Occlusion factor for this pixel, where 0 is completely unoccluded and 1 is fully occluded.
	float OccludedPercent = 0;

	int NumOcclusionSampleCount = 8;

	UNROLL
	for (int i = 0; i < NumOcclusionSampleCount; i += 4)
	{
		// Operate on 4 samples at a time to take advantage of vectorized instructions
		// Reflect each sample across a random normal, scale by radius
		// This is necessary to hide the low sample count and view dependence of the results.
		// Calculate view space sample position
		//@todo - calculate sample positions in screenspace to save instructions

		float3 SamplePosition0 = ViewSpacePosition + (OcclusionSamplesOffsets2[i].xyz - dot(OcclusionSamplesOffsets2[i].xyz, RandomNormal) * RandomNormalX2) * Radius.xxx; 
		float3 SamplePosition1 = ViewSpacePosition + (OcclusionSamplesOffsets2[i + 1].xyz - dot(OcclusionSamplesOffsets2[i + 1].xyz, RandomNormal) * RandomNormalX2) * Radius.xxx; 
		float3 SamplePosition2 = ViewSpacePosition + (OcclusionSamplesOffsets2[i + 2].xyz - dot(OcclusionSamplesOffsets2[i + 2].xyz, RandomNormal) * RandomNormalX2) * Radius.xxx; 
		float3 SamplePosition3 = ViewSpacePosition + (OcclusionSamplesOffsets2[i + 3].xyz - dot(OcclusionSamplesOffsets2[i + 3].xyz, RandomNormal) * RandomNormalX2) * Radius.xxx; 

		float2 InvSampleZ01 = 1.0f / float2(SamplePosition0.z, SamplePosition1.z);
		float2 InvSampleZ23 = 1.0f / float2(SamplePosition2.z, SamplePosition3.z);

		// Transform the positions into screen space 
#if ARBITRARY_PROJECTION
		// Handle projection matrices which scale and translate x and y
		float2 SampleProjPostion0 = MulMatrix(ProjectionMatrix,float4(SamplePosition0, 1)).xy;
		float2 SampleProjPostion1 = MulMatrix(ProjectionMatrix,float4(SamplePosition1, 1)).xy;
		float4 SampleCoords01 = float4(SampleProjPostion0.xy, SampleProjPostion1.xy) * InvSampleZ01.xxyy * AOScreenPositionScaleBias.xyxy + AOScreenPositionScaleBias.zwzw;
		float2 SampleProjPostion2 = MulMatrix(ProjectionMatrix,float4(SamplePosition2, 1)).xy;
		float2 SampleProjPostion3 = MulMatrix(ProjectionMatrix,float4(SamplePosition3, 1)).xy;
		float4 SampleCoords23 = float4(SampleProjPostion2.xy, SampleProjPostion3.xy) * InvSampleZ23.xxyy * AOScreenPositionScaleBias.xyxy + AOScreenPositionScaleBias.zwzw;
#else
		// Optimized path for normal projection matrices which only scale x and y
		float4 SampleCoords01 = float4(SamplePosition0.xy, SamplePosition1.xy) * ProjectionScale.xyxy * InvSampleZ01.xxyy + AOScreenPositionScaleBias.zwzw;
		float4 SampleCoords23 = float4(SamplePosition2.xy, SamplePosition3.xy) * ProjectionScale.xyxy * InvSampleZ23.xxyy + AOScreenPositionScaleBias.zwzw;
#endif

		float2 Sample0 = CalcOcclusionAndDepth(SampleCoords01.xy);
		float2 Sample1 = CalcOcclusionAndDepth(SampleCoords01.zw);
		float2 Sample2 = CalcOcclusionAndDepth(SampleCoords23.xy);
		float2 Sample3 = CalcOcclusionAndDepth(SampleCoords23.zw);

		// View space z of the nearest opaque occluder at the sample positions
		float4 OccluderDepths = float4(Sample0.y, Sample1.y, Sample2.y, Sample3.y);

		// Difference in view space z between the samples and the nearest opaque occluder
		float4 DepthDeltas = OccluderDepths - float4(SamplePosition0.z, SamplePosition1.z, SamplePosition2.z, SamplePosition3.z);

		// Soft comparison (instead of DepthDeltas<0.0f),  tweaked constant
		float4 NewDepthDeltas = saturate(-0.15f * DepthDeltas);

		// 0.5f means unknown, better keep this hard coded (instead of OcclusionCalcParameters.wwww)
		NewDepthDeltas = DepthDeltas < HaloDistanceThreshold.xxxx ? 0.5f : NewDepthDeltas;

		OccludedPercent += dot(NewDepthDeltas, 1.0f / NumOcclusionSampleCount);
	}

	float UnOccludedPercent = 1 - OccludedPercent;

	// Apply contrast and brightness remappings to the unoccluded factor.
	// @todo - need depth-dependent controls over contrast and brightness since distant occlusion gets lighter after blurring
	UnOccludedPercent = saturate(pow(UnOccludedPercent, OcclusionRemapParameters.x) * OcclusionRemapParameters.y + OcclusionRemapParameters.z);

	// Fade out occlusion based on distance, used to hide artifacts on distant objects.
	UnOccludedPercent = lerp(UnOccludedPercent, 1.0f, saturate((SceneDepth - OcclusionFadeoutParameters.x) * OcclusionFadeoutParameters.y)); 

	// Clamp the unoccluded percent to the adjustable minimum
	// Output occlusion and depth
	OutColor = OutputOcclusionAndDepth(max(UnOccludedPercent, OcclusionRemapParameters.w), SceneDepth);
}
#else	// AO_ANGLEBASED
void OcclusionPixelMain(
	in FAOVertexOutput In,
	out float4 OutColor : COLOR0
	)
{
	float SceneDepth = CalcDownsampledDepth(In.TexCoord);

	// Calculate view space position of the current pixel
	float3 ViewSpacePosition = In.ViewScreenVector.xyz * SceneDepth;
	// Get a random normal for this pixel
	half3 RandomNormal = tex2D(RandomNormalTexture, In.TexCoord * NoiseScale.xy).xyz;

	// PS3 texture lookup already does *2-1
#if !PS3
	RandomNormal = RandomNormal*2-1;
#endif

	half3 RandomNormalX2 = RandomNormal * 2.0f;

#if AO_QUALITY == 0 || AO_QUALITY == 1
	// Clamp the radius in screen space to provide an upper bound on texture cache thrashing, which is esp needed on xenon.
	float Radius = min(MaxRadiusTransform * SceneDepth, OcclusionCalcParameters.x);
#else
	float Radius = OcclusionCalcParameters.xxx;
#endif

	float HaloDistanceThreshold = -OcclusionCalcParameters.z - HaloDistanceScale * SceneDepth;

	int NumOcclusionSampleCount = 8;

	float WeightSum = 1;
	float ret = 0;
	UNROLL
	for (int i = 0; i < NumOcclusionSampleCount / 2; ++i)
	{
		float3 RandomDirection = (OcclusionSamplesOffsets3[i].xyz - dot(OcclusionSamplesOffsets3[i].xyz, RandomNormal) * RandomNormalX2) * Radius.xxx;
		float3 SamplePosition0 = ViewSpacePosition + RandomDirection; 
		float3 SamplePosition1 = ViewSpacePosition - RandomDirection; 

		float2 InvSampleZ01 = 1.0f / float2(SamplePosition0.z, SamplePosition1.z);

		// Transform the positions into screen space 
#if ARBITRARY_PROJECTION
		// Handle projection matrices which scale and translate x and y
		float2 SampleProjPostion0 = MulMatrix(ProjectionMatrix,float4(SamplePosition0, 1)).xy;
		float2 SampleProjPostion1 = MulMatrix(ProjectionMatrix,float4(SamplePosition1, 1)).xy;
		float4 SampleCoords01 = float4(SampleProjPostion0.xy, SampleProjPostion1.xy) * InvSampleZ01.xxyy * AOScreenPositionScaleBias.xyxy + AOScreenPositionScaleBias.zwzw;
#else
		// Optimized path for normal projection matrices which only scale x and y
		float4 SampleCoords01 = float4(SamplePosition0.xy, SamplePosition1.xy) * ProjectionScale.xyxy * InvSampleZ01.xxyy + AOScreenPositionScaleBias.zwzw;
#endif

		float2 Sample0 = CalcOcclusionAndDepth(SampleCoords01.xy);
		float2 Sample1 = CalcOcclusionAndDepth(SampleCoords01.zw);

		// View space z of the nearest opaque occluder at the sample positions
		float leftAbs = Sample0.y;
		float rightAbs = Sample1.y;

		float left = leftAbs - SceneDepth;
		float right = rightAbs - SceneDepth;

		SamplePosition0 = SamplePosition0 * (leftAbs / SceneDepth);
		SamplePosition1 = SamplePosition1 * (rightAbs / SceneDepth);

	    float3 DeltaLeft = SamplePosition0 - ViewSpacePosition;
	    float3 DeltaRight = SamplePosition1 - ViewSpacePosition;
	    float3 Delta = SamplePosition0 - SamplePosition1;

	    float NormAngle = acosApproxNormAngle( dot(DeltaLeft, DeltaRight) / sqrt(length2(DeltaLeft) * length2(DeltaRight)));
	   	
		// not 100% correct but simple
	    // bias is needed to avoid flickering on almost perfectly flat surfaces
	    FLATTEN
		if((leftAbs + rightAbs) * 0.5f > SceneDepth - 0.00001f)
	    {
		    // fix concave case
		    NormAngle = 1;
	    }

		float Weight = min(left > HaloDistanceThreshold, right > HaloDistanceThreshold);

		ret += (1 - NormAngle) * Weight;
		WeightSum += Weight;
	}

	ret /= WeightSum;

	float UnOccludedPercent = 1 - ret;

	// Apply contrast and brightness remappings to the unoccluded factor.
	// @todo - need depth-dependent controls over contrast and brightness since distant occlusion gets lighter after blurring
	UnOccludedPercent = saturate(pow(UnOccludedPercent, OcclusionRemapParameters.x) * OcclusionRemapParameters.y + OcclusionRemapParameters.z);

	// Fade out occlusion based on distance, used to hide artifacts on distant objects.
	UnOccludedPercent = lerp(UnOccludedPercent, 1.0f, saturate((SceneDepth - OcclusionFadeoutParameters.x) * OcclusionFadeoutParameters.y)); 

	// Clamp the unoccluded percent to the adjustable minimum
	// Output occlusion and depth
	OutColor = OutputOcclusionAndDepth(max(UnOccludedPercent, OcclusionRemapParameters.w), SceneDepth);
}
#endif	// AO_ANGLEBASED

/*-----------------------------------------------------------------------------
	Filter
-----------------------------------------------------------------------------*/

void FilterVertexMain(
	in float4 InPosition : POSITION,
	in float2 UV : TEXCOORD0,
	out float2 OutCenterUV : TEXCOORD0,
	out float4 OutPosition : POSITION
	)
{
	OutPosition = InPosition;
	OutCenterUV = UV.xy;
}

/** EdgeDistanceThreshold, EdgeDistanceScale */
float4 FilterParameters;

/** AmbientOcclusionBufferSizeX, AmbientOcclusionBufferSizeY, 1 / AmbientOcclusionBufferSizeX, 1 / AmbientOcclusionBufferSizeY */
float4 CustomParameters;

/*
 * Separable filter used to reduce spatial noise.  Uses depth to identify object boundaries and avoid filtering across them.
 */
void FilterPixelMain(
	float2 InCenterUV : TEXCOORD0,
	out float4 OutColor : COLOR0
	)
{
	// Occlusion value in x, Depth in y
	float2 CenterOcclusionAndDepth = CalcOcclusionAndDepth(InCenterUV);
	// Calculate a kernel scale factor inversely proportional to depth.
	// This is used to decrease the blur amount on distant pixels which preserves detail and unfortunately noise.
	float KernelScale = clamp(FilterParameters.z / CenterOcclusionAndDepth.y, .5f, 1.0f);
	// Calculate a depth delta scale based on distance, so that nearby edges are calculated more accurately and large slopes at a distance are not identified as edges.
	float DeltaScale = clamp(FilterParameters.y * CenterOcclusionAndDepth.y, 1.0f, 100.0f);
	// Depth delta that will be used to identify edges
	half DepthDelta = FilterParameters.x * DeltaScale;
	// Start out with the current occlusion value
	half Sum = CenterOcclusionAndDepth.x;
	// Stores an accumulation of valid weights, starts out with 1 as the current occlusion value is fully weighted.
	half TotalWeight = 1.0f;

	Sum = 0;
	TotalWeight = 0;

	UNROLL for(int y = -2; y < 2; ++y)
	{
		UNROLL for(int x = -2; x < 2; ++x)
		{
			// Apply offsets for each sample, scaling by the depth-dependent KernelScale
			float2 SampleOffsets = InCenterUV + CustomParameters.zw * float2( x, y);
			// Find depth and occlusion at the current pixel
			float2 SampleOcclusionAndDepths = CalcOcclusionAndDepth(SampleOffsets);

			// If the sample's depth is within DepthDelta units from the current pixel's depth, consider it a valid sample,
			// which avoids filtering across object boundaries as determined by depth only.
			float DepthCompares = 1 - saturate(0.0001f * Square(SampleOcclusionAndDepths.y - CenterOcclusionAndDepth.y));

			// if there is very little others to blend we rater take that (less noise in gras)
			DepthCompares += 0.2f;

			TotalWeight += DepthCompares;

			// Accumulate occlusion values from valid samples
			Sum += SampleOcclusionAndDepths.x * DepthCompares;
		}
	}

	// Pass depth through, Normalize and output the filtered occlusion value
	OutColor = OutputOcclusionAndDepth(Sum / TotalWeight, CenterOcclusionAndDepth.y);
}

float4x4 ScreenToWorldOffset;

/*-----------------------------------------------------------------------------
	History Update
-----------------------------------------------------------------------------*/

void HistoryUpdateVertexMain(
	in float4 InPosition : POSITION,
	in float2 InTexCoord : TEXCOORD0,
	out float2 OutTexCoord : TEXCOORD0,
	out float3 OutScreenVector : TEXCOORD1,
	out float4 OutPosition : POSITION
	)
{
	OutPosition = InPosition;
	OutTexCoord = InTexCoord;

	OutScreenVector = MulMatrix(ScreenToWorldOffset, float4(InPosition.xy, 1, 0));		
}

/** Last frame's view projection matrix. */
float4x4 PrevViewProjMatrix;

/** Weight to lerp between the current occlusion value and the running history. */
float2 HistoryConvergenceRates;

/** Screen space extents, used to identify new pixels on the edges of the screen which don't have a history. */
float4 ScreenEdgeLimits;

/**
 * Gets the history's occlusion and convergence stored at PrevScreenCoord and 
 */
void HistoryUpdateCommon(in float2 PrevScreenCoord, in float CurrentOcclusion, out float UpdatedOcclusion, out float UpdatedConvergence)
{
	float2 HistoryOcclusionAndConvergence = CalcHistoryOcclusionAndConvergence(PrevScreenCoord);

#if !XBOX && !PS3
	// Clamp the history max to its valid range.  It can be out of range if the history buffer was not cleared initially.
	HistoryOcclusionAndConvergence.x = min(HistoryOcclusionAndConvergence.x, 1.0f);
#endif
	
	// HistoryOcclusionAndConvergence.y stores a running convergence weight, used to accelerate convergence after a history reset.
	float Weight = HistoryConvergenceRates.x * (1.0f - HistoryOcclusionAndConvergence.y);

	// Check if the reprojected pixel was off the screen last frame
	float EdgeResult = all(float4(PrevScreenCoord.xy > ScreenEdgeLimits.xy, PrevScreenCoord.xy < ScreenEdgeLimits.zw));

	FLATTEN
	if (EdgeResult < .05)
	{
		// Discard history for new pixels on the edges of the screen
		Weight = 0;
		// Reset the convergence weight
		HistoryOcclusionAndConvergence.y = 1;
	}

	// Increase the running convergence (0 is fully converged, 1 is not converged)
	HistoryOcclusionAndConvergence.y = saturate(HistoryOcclusionAndConvergence.y - HistoryConvergenceRates.y);

	// @todo: Apply contrast and brightness remapping here instead of before filtering?
	// Interpolate between the current value and the history value based on Weight
	UpdatedOcclusion = saturate(lerp(CurrentOcclusion, HistoryOcclusionAndConvergence.x, Weight));
	UpdatedConvergence = HistoryOcclusionAndConvergence.y;
}

/*
 * Updates the history for every pixel in the history buffer.
 * Uses SceneDepth to calculate world position and does not handle moving objects.
 */
void StaticHistoryUpdatePixelMain(
	in float2 InTexCoord : TEXCOORD0,
	in float3 InScreenVector : TEXCOORD1,
	out float4 OutColor : COLOR0
	)
{
	// Find the view space depth at the current pixel
	float2 CurrentOcclusionAndDepth = CalcOcclusionAndDepth(InTexCoord);
	// Calculate the world space position offset by camera position of the nearest opaque object
	float3 OffsetWorldPos = InScreenVector * CurrentOcclusionAndDepth.y;
	// Assuming this frame's world position is the same as last frame's, for the current pixel.  That is why this only works for non-moving objects.
	// Transform into the previous frame's clip space
	float4 PrevClipPos = MulMatrix(PrevViewProjMatrix, float4(OffsetWorldPos, 1));
	// Transform into the previous frame's screen coordinates
	float2 PrevScreenCoord = PrevClipPos.xy / PrevClipPos.w * AOScreenPositionScaleBias.xy + AOScreenPositionScaleBias.zw;
	
	float UpdatedOcclusion;
	float UpdatedConvergence;
	HistoryUpdateCommon(PrevScreenCoord, CurrentOcclusionAndDepth.x, UpdatedOcclusion, UpdatedConvergence);

	// Update the running history with the new occlusion and convergence factor
	OutColor = float4(UpdatedOcclusion, UpdatedConvergence, 0, 0);
}

/*
 * Attempts to do depth testing manually, by clipping based on depth.  
 * Obviously this has lots of limitations and is not performant, but is necessary when the depth buffer cannot be used.
 */
void ManualDepthTest(float CurrentDepth, float ExistingDepth)
{
	float Slope = min(max(abs(ddx(CurrentDepth)), abs(ddy(CurrentDepth))), 10.0f);
	clip(ExistingDepth - CurrentDepth + 1 + Slope * 2.0f);
}

/*
 * Updates the history for moving meshes which do support a precise previous world position.
 */
void DynamicHistoryUpdatePixelMain(
	float4 InScreenPosition : TEXCOORD0,
	float4 InPrevScreenPosition : TEXCOORD1,
	out float4 OutColor : COLOR0
	)
{
	float2 ScreenAlignedPosition = InScreenPosition.xy / InScreenPosition.w * AOScreenPositionScaleBias.xy + AOScreenPositionScaleBias.zw;
	float2 CurrentOcclusionAndDepth = CalcOcclusionAndDepth(ScreenAlignedPosition);

#if USE_MANUAL_DEPTH_TEST
	// Do depth testing manually, since the depth buffer is a different size than the history render target
	ManualDepthTest(InScreenPosition.w, CurrentOcclusionAndDepth.y);
#endif

	float2 PrevScreenCoord = InPrevScreenPosition.xy / InPrevScreenPosition.w * AOScreenPositionScaleBias.xy + AOScreenPositionScaleBias.zw;
	
	float UpdatedOcclusion;
	float UpdatedConvergence;
	HistoryUpdateCommon(PrevScreenCoord, CurrentOcclusionAndDepth.x, UpdatedOcclusion, UpdatedConvergence);

	// Update the running history with the new occlusion and convergence factor
	OutColor = float4(UpdatedOcclusion, UpdatedConvergence, 0.0f, 0.0f);
}

float4 OcclusionColor;

/*-----------------------------------------------------------------------------
	Apply
-----------------------------------------------------------------------------*/

void AOApplyMain(
	in FAOVertexOutput In,
	out float4 OutColor : COLOR0
	)
{
#if APPLY_FROM_AOHISTORY
	float Occlusion = CalcHistoryOcclusion(In.TexCoord);
#else
	float Occlusion = CalcOcclusion(In.TexCoord);
#endif

	OutColor = RETURN_COLOR(float4(OcclusionColor.rgb, Occlusion));
}

#if XBOX

/** Size of FogFactorTexture, in pixels */
float2 TargetSize;

/** Lighting contribution from fog.  This uses 4 components to avoid platform-specific alignment of each element of the array. */
half4	FogInScattering;

/** Inverse of power to apply to the fog factor to increase nearby precision, while hurting distant precision. */
half InvEncodePower;

void AOAndFogApplyMain(
	in FAOVertexOutput In,
	out float4 OutColor : COLOR0
	)
{
#if APPLY_FROM_AOHISTORY
	float Occlusion = CalcHistoryOcclusion(In.TexCoord);
#else
	float Occlusion = CalcOcclusion(In.TexCoord);
#endif
	// read the fog values encoded during DownsampleDepthAndFogMain 
	const half4 FogInput = tex2D(FogFactorTexture,In.TexCoord);
	
	// Fog values were encoded as exp(x*8) to make better use of the 8bit range.
	// We restore the fog samples to the original curve using the exponent 1/8.
	const half4 FogFactors = pow(FogInput, InvEncodePower);
	
	int2 FractionalTexcoord = floor(fmod(In.TexCoord*TargetSize, float2(2,2)));
	int Index = FractionalTexcoord.x + 2*FractionalTexcoord.y;
	half FogScattering = FogFactors[Index];
	half InScattering = FogScattering - 1;	
	half3 FogColor = InScattering * FogInScattering.rgb;	

	// Ambient occlusion is blended as FinalColor = OcclusionColor * (1.0f - OcclusionFactor) + DestColor * OcclusionFactor
	// Height fog is blended as FinalColor = FogColor + DestColor * FogFactor
	// Combine these blend functions, treating ambient occlusion as being filtered through the fog
	// FinalColor = FogColor + FogFactor * OcclusionColor * (1.0f - OcclusionFactor) + DestColor * OcclusionFactor * FogFactor
	// So using a blend mode of TStaticBlendState<BO_Add,BF_One,BF_SourceAlpha>:
	half3 AddColor = FogColor + FogScattering * OcclusionColor.rgb * (1.0f - Occlusion);
	OutColor = RETURN_COLOR(float4(AddColor, Occlusion * FogScattering));
}

#endif