#include "/lib/settings.glsl"

layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
const vec2 workGroupsRender = vec2(
	FOXY_RAY_RESOLUTION,
	FOXY_RAY_RESOLUTION
);

layout(rgba16f) readonly uniform image2D img_ptHistoryMetaA;
layout(rgba16f) readonly uniform image2D img_ptHistoryMetaB;
#if FOXY_SSPT_ATROUS_PASS == 0
	layout(rgba16f) readonly uniform image2D img_ptHistoryA;
	layout(rgba16f) readonly uniform image2D img_ptHistoryB;
	layout(rg16f) readonly uniform image2D img_ptMomentsA;
	layout(rg16f) readonly uniform image2D img_ptMomentsB;
#endif

#if FOXY_SSPT_ATROUS_PASS == 0
	layout(rgba16f) writeonly uniform image2D img_ptFilteredA;
#elif FOXY_SSPT_ATROUS_PASS == 1 || FOXY_SSPT_ATROUS_PASS == 3 || FOXY_SSPT_ATROUS_PASS == 5
	layout(rgba16f) readonly uniform image2D img_ptFilteredA;
	layout(rgba16f) writeonly uniform image2D img_ptFiltered;
#else
	layout(rgba16f) readonly uniform image2D img_ptFiltered;
	layout(rgba16f) writeonly uniform image2D img_ptFilteredA;
#endif

uniform mat4 gbufferModelView;
uniform mat4 gbufferProjection;
uniform sampler2D depthtex0;
uniform float viewWidth;
uniform float viewHeight;
uniform vec2 temporalJitter;
uniform int frameCounter;

#include "/lib/math.glsl"
#include "/lib/sr.glsl"
#include "/lib/rt_denoiser.glsl"
#include "/lib/first_person_depth.glsl"
#define PT_GBUFFER_READ
#include "/lib/pt_gbuffer.glsl"
#undef PT_GBUFFER_READ

#define SSPT_FILTER_GROUP_SIZE 16
#if FOXY_SSPT_ATROUS_PASS == 0
	#define SSPT_FILTER_JITTER_RADIUS 0
#else
	#define SSPT_FILTER_JITTER_RADIUS ((FOXY_SSPT_ATROUS_STEP + 1) / 2)
#endif
#define SSPT_FILTER_RADIUS (FOXY_SSPT_ATROUS_STEP + SSPT_FILTER_JITTER_RADIUS)
#define SSPT_FILTER_TILE_SIDE (SSPT_FILTER_GROUP_SIZE + SSPT_FILTER_RADIUS * 2)
#define SSPT_FILTER_TILE_AREA (SSPT_FILTER_TILE_SIDE * SSPT_FILTER_TILE_SIDE)

// All three presets use a fixed full -> cross -> cross schedule. It keeps the
// first pass isotropic, while each wide pass uses
// four neighbors instead of eight; preset differences live in the final
// robust visibility limiter and never change geometry/material classification.
#ifdef FOXY_SSPT_ATROUS_DENSE
	#define SSPT_FILTER_PATTERN 0
#elif FOXY_SSPT_ATROUS_PASS > 0
	#define SSPT_FILTER_PATTERN 1
#else
	#define SSPT_FILTER_PATTERN 0
#endif

#if SSPT_FILTER_PATTERN == 1
	#define SSPT_AXIS_WEIGHT 0.1875
#else
	#define SSPT_AXIS_WEIGHT 0.1250
#endif
#if SSPT_FILTER_PATTERN == 2
	#define SSPT_DIAGONAL_WEIGHT 0.1875
#else
	#define SSPT_DIAGONAL_WEIGHT 0.0625
#endif

#if FOXY_SSPT_ATROUS_SHARED == 1
shared vec4 ssptSharedSignal[SSPT_FILTER_TILE_AREA];
// View guide remains vec4 so taps avoid an oct-normal decode.  Split the
// position into scalar arrays: three floats have the same 12-byte payload as
// the packed guide, but avoid a per-tap decode/normalization path.
shared vec4 ssptSharedViewGuide[SSPT_FILTER_TILE_AREA];
shared float ssptSharedViewPositionX[SSPT_FILTER_TILE_AREA];
shared float ssptSharedViewPositionY[SSPT_FILTER_TILE_AREA];
shared float ssptSharedViewPositionZ[SSPT_FILTER_TILE_AREA];
// These metadata fields are the only parts consumed by a shared tap.  Cache
// them as scalars instead of carrying/decoding the original vec4 marker.
// RtDenoiserPackMetaState clamps every valid age to [1, 15], so age==0
// is also a lossless shared-path validity marker.
shared float ssptSharedMetaAge[SSPT_FILTER_TILE_AREA];
shared float ssptSharedMetaSurfaceClass[SSPT_FILTER_TILE_AREA];
shared float ssptSharedFirstPerson[SSPT_FILTER_TILE_AREA];
	#if FOXY_SSPT_ATROUS_PASS == 0
shared vec4 ssptSharedMoments[SSPT_FILTER_TILE_AREA];
	#endif
#endif

// Per-invocation reconstruction constants.  Every tap in an invocation shares
// these values, so compute the image ratio, projection reciprocals and grazing
// relaxation once instead of rebuilding them four or eight times.
ivec2 ssptAtrousFilterSize;
ivec2 ssptAtrousRenderSize;
vec2 ssptAtrousRayToRenderScale;
vec2 ssptAtrousViewPixelScale;
vec2 ssptAtrousViewBias;
float ssptAtrousCenterGrazingRelax;

vec3 SsptAtrousViewPosition(
	const in ivec2 tracePixel,
	const in float viewDepth,
	const in float primaryOffsetIndex
);

float SsptAtrousFirstPerson(
	const in ivec2 tracePixel,
	const in vec4 meta
);

bool SsptAtrousReadsA() {
	return (frameCounter % 2) == 0;
}

#if FOXY_SSPT_ATROUS_PASS == 0
	vec4 SsptAtrousHistorySignal(const in ivec2 pixel) {
		if (SsptAtrousReadsA()) return imageLoad(img_ptHistoryA, pixel);
		return imageLoad(img_ptHistoryB, pixel);
	}
#endif

vec4 SsptAtrousMeta(const in ivec2 pixel) {
	if (SsptAtrousReadsA()) return imageLoad(img_ptHistoryMetaA, pixel);
	return imageLoad(img_ptHistoryMetaB, pixel);
}

#if FOXY_SSPT_ATROUS_PASS == 0
	vec4 SsptAtrousMoments(const in ivec2 pixel) {
		if (SsptAtrousReadsA()) return imageLoad(img_ptMomentsA, pixel);
		return imageLoad(img_ptMomentsB, pixel);
	}
#endif

vec4 SsptAtrousSource(const in ivec2 pixel) {
	#if FOXY_SSPT_ATROUS_PASS == 0
		return SsptAtrousHistorySignal(pixel);
	#elif FOXY_SSPT_ATROUS_PASS == 1 || FOXY_SSPT_ATROUS_PASS == 3 || FOXY_SSPT_ATROUS_PASS == 5
		return imageLoad(img_ptFilteredA, pixel);
	#else
		return imageLoad(img_ptFiltered, pixel);
	#endif
}

ivec2 SsptAtrousOutputSize() {
	#if FOXY_SSPT_ATROUS_PASS == 1 || FOXY_SSPT_ATROUS_PASS == 3 || FOXY_SSPT_ATROUS_PASS == 5
		return SrActiveRaySceneSize(imageSize(img_ptFiltered));
	#else
		return SrActiveRaySceneSize(imageSize(img_ptFilteredA));
	#endif
}

void SsptAtrousStore(const in ivec2 pixel, const in vec4 value) {
	#if FOXY_SSPT_ATROUS_PASS == 1 || FOXY_SSPT_ATROUS_PASS == 3 || FOXY_SSPT_ATROUS_PASS == 5
		imageStore(img_ptFiltered, pixel, value);
	#else
		imageStore(img_ptFilteredA, pixel, value);
	#endif
}

#if FOXY_SSPT_ATROUS_SHARED == 1
int SsptAtrousSharedIndex(const in ivec2 localPixel) {
	return localPixel.y * SSPT_FILTER_TILE_SIDE + localPixel.x;
}

void SsptAtrousPreload(const in ivec2 filterSize) {
	ivec2 groupBase = ivec2(gl_WorkGroupID.xy) * SSPT_FILTER_GROUP_SIZE - ivec2(SSPT_FILTER_RADIUS);
	int localIndex = int(gl_LocalInvocationIndex);
	for (int tileIndex = localIndex; tileIndex < SSPT_FILTER_TILE_AREA; tileIndex += SSPT_FILTER_GROUP_SIZE * SSPT_FILTER_GROUP_SIZE) {
		ivec2 tilePixel = ivec2(tileIndex % SSPT_FILTER_TILE_SIDE, tileIndex / SSPT_FILTER_TILE_SIDE);
		ivec2 sourcePixel = clamp(groupBase + tilePixel, ivec2(0), filterSize - ivec2(1));
		ssptSharedSignal[tileIndex] = SsptAtrousSource(sourcePixel);
		vec4 sourceMeta = SsptAtrousMeta(sourcePixel);
		bool sourceMetaFinite = RtDenoiserFinite4(sourceMeta);
		float sourceMetaValid = sourceMetaFinite
			? RtDenoiserMetaValid(sourceMeta)
			: 0.0;
		ssptSharedMetaAge[tileIndex] = sourceMetaValid > 0.5
			? RtDenoiserMetaAge(sourceMeta)
			: 0.0;
		ssptSharedMetaSurfaceClass[tileIndex] = sourceMetaFinite
			? RtDenoiserMetaSurfaceClass(sourceMeta)
			: 0.0;
		vec3 sourceViewNormal = vec3(0.0, 0.0, 1.0);
		float sourceDepth = sourceMetaFinite ? max(sourceMeta.z, 1.0e-3) : 1.0;
		float sourceOffsetIndex = sourceMetaFinite
			? RtDenoiserMetaPrimaryOffsetIndex(sourceMeta)
			: 0.0;
		ssptSharedFirstPerson[tileIndex] = sourceMetaValid > 0.5
			? SsptAtrousFirstPerson(sourcePixel, sourceMeta)
			: 0.0;
		if (sourceMetaValid > 0.5) {
			sourceViewNormal = normalize(mat3(gbufferModelView) * PtDecodeOctNormal(sourceMeta.xy));
		}
		ssptSharedViewGuide[tileIndex] = vec4(sourceViewNormal, sourceDepth);
		vec3 sourceViewPosition = SsptAtrousViewPosition(
			sourcePixel,
			sourceDepth,
			sourceOffsetIndex
		);
		ssptSharedViewPositionX[tileIndex] = sourceViewPosition.x;
		ssptSharedViewPositionY[tileIndex] = sourceViewPosition.y;
		ssptSharedViewPositionZ[tileIndex] = sourceViewPosition.z;
		#if FOXY_SSPT_ATROUS_PASS == 0
			ssptSharedMoments[tileIndex] = SsptAtrousMoments(sourcePixel);
		#endif
	}
	memoryBarrierShared();
	barrier();
}
#endif

vec3 SsptAtrousViewPosition(
	const in ivec2 tracePixel,
	const in float viewDepth,
	const in float primaryOffsetIndex
) {
	vec2 primaryPixel = vec2(SrRayPrimaryPixelScaled(
		tracePixel,
		ssptAtrousRayToRenderScale,
		ssptAtrousRenderSize,
		int(floor(primaryOffsetIndex + 0.5))
	));
	vec2 viewRay = (primaryPixel + vec2(0.5)) *
		ssptAtrousViewPixelScale + ssptAtrousViewBias;
	return vec3(viewRay * viewDepth, -viewDepth);
}

float SsptAtrousFirstPerson(
	const in ivec2 tracePixel,
	const in vec4 meta
) {
	ivec2 primaryPixel = SrRayPrimaryPixelScaled(
		tracePixel,
		ssptAtrousRayToRenderScale,
		ssptAtrousRenderSize,
		int(floor(RtDenoiserMetaPrimaryOffsetIndex(meta) + 0.5))
	);
	float rawDepth = texelFetch(depthtex0, primaryPixel, 0).r;
	float dynamicSurface = 1.0 - step(
		0.5,
		abs(RtDenoiserMetaSurfaceClass(meta) - PT_SURFACE_DYNAMIC)
	);
	return FirstPersonDepthMask(rawDepth) * dynamicSurface;
}

float SsptAtrousThinSurface(const in float surfaceClass) {
	float plant = 1.0 - step(0.5, abs(surfaceClass - PT_SURFACE_PLANT));
	float strand = 1.0 - step(0.5, abs(surfaceClass - PT_SURFACE_STRAND));
	return max(plant, strand);
}

vec3 SsptAtrousThinViewNormal(const in vec3 viewPosition) {
	float viewLengthSquared = dot(viewPosition, viewPosition);
	if (viewLengthSquared <= 1.0e-6) return vec3(0.0, 0.0, 1.0);
	vec3 viewToCamera = -viewPosition * inversesqrt(viewLengthSquared);
	vec3 strandTangent = normalize(mat3(gbufferModelView) * vec3(0.0, 1.0, 0.0));
	vec3 projectedNormal = viewToCamera - strandTangent * dot(viewToCamera, strandTangent);
	float projectedLengthSquared = dot(projectedNormal, projectedNormal);
	return projectedLengthSquared > 1.0e-6
		? projectedNormal * inversesqrt(projectedLengthSquared)
		: viewToCamera;
}

void SsptAtrousTap(
	const in ivec2 centerPixel,
	const in ivec2 centerLocalPixel,
	const in ivec2 unitOffset,
	const in ivec2 sampleShift,
	const in float kernelWeight,
	const in vec3 centerViewNormal,
	const in vec3 centerViewPosition,
	const in float centerDepth,
	const in float centerSurfaceClass,
	const in float centerLuminance,
	const in float centerVariance,
	const in float centerFirstPerson,
	const in float filterAmount,
	inout vec3 radianceSum,
	inout float varianceNumerator,
	inout float weightSum,
	inout float geometrySupportSum
) {
	ivec2 tapOffset = unitOffset * FOXY_SSPT_ATROUS_STEP + sampleShift;
	ivec2 samplePixel = centerPixel + tapOffset;
	if (any(lessThan(samplePixel, ivec2(0))) || any(greaterThanEqual(samplePixel, ssptAtrousFilterSize))) return;
	vec4 sampleSignal;
	vec4 sampleViewGuide;
	vec3 sampleViewPosition;
	float sampleAge;
	float sampleSurfaceClass;
	float markerValid;
	#if FOXY_SSPT_ATROUS_SHARED == 0
		vec4 sampleMeta;
	#endif
	#if FOXY_SSPT_ATROUS_PASS == 0
		vec4 sampleMoments;
	#endif
	#if FOXY_SSPT_ATROUS_SHARED == 1
		ivec2 sampleLocalPixel = centerLocalPixel + tapOffset;
		int sampleIndex = SsptAtrousSharedIndex(sampleLocalPixel);
		sampleSignal = ssptSharedSignal[sampleIndex];
		sampleViewGuide = ssptSharedViewGuide[sampleIndex];
		sampleViewPosition = vec3(
			ssptSharedViewPositionX[sampleIndex],
			ssptSharedViewPositionY[sampleIndex],
			ssptSharedViewPositionZ[sampleIndex]
		);
		#if FOXY_SSPT_ATROUS_PASS == 0
			sampleMoments = ssptSharedMoments[sampleIndex];
			sampleAge = sampleSignal.a;
		#else
			sampleAge = ssptSharedMetaAge[sampleIndex];
		#endif
		sampleSurfaceClass = ssptSharedMetaSurfaceClass[sampleIndex];
		markerValid = step(0.5, ssptSharedMetaAge[sampleIndex]);
	#else
		sampleSignal = SsptAtrousSource(samplePixel);
		sampleMeta = SsptAtrousMeta(samplePixel);
		#if FOXY_SSPT_ATROUS_PASS == 0
			sampleMoments = SsptAtrousMoments(samplePixel);
			sampleAge = sampleSignal.a;
		#else
			sampleAge = RtDenoiserMetaAge(sampleMeta);
		#endif
		sampleSurfaceClass = RtDenoiserMetaSurfaceClass(sampleMeta);
		markerValid = RtDenoiserMetaValid(sampleMeta);
	#endif
	// Pass 0 consumes the temporal history and must validate all payloads.
	// Every later pass reads the result of SsptAtrousStore(), which clamps
	// radiance/variance to finite ranges; repeating four finite predicates per
	// tap there only adds control-flow and comparison cost.  Keep the metadata
	// marker check below for geometric rejection, while retaining a conservative
	// finite check on the first pass.
	#if FOXY_SSPT_ATROUS_PASS == 0
		#if FOXY_SSPT_ATROUS_SHARED == 1
			bool sampleFinite = RtDenoiserFinite4(sampleSignal)
				&& RtDenoiserFinite4(sampleMoments);
		#else
			bool sampleFinite = RtDenoiserFinite4(sampleSignal)
				&& RtDenoiserFinite4(sampleMeta)
				&& RtDenoiserFinite4(sampleMoments);
		#endif
	#else
		#if FOXY_SSPT_ATROUS_SHARED == 1
			bool sampleFinite = true;
		#else
			bool sampleFinite = RtDenoiserFinite4(sampleMeta);
		#endif
	#endif
	if (sampleAge < 0.5 || !sampleFinite) return;
	if (markerValid < 0.5) return;
	#if FOXY_SSPT_ATROUS_SHARED == 1
		vec3 sampleViewNormal = sampleViewGuide.xyz;
		float sampleDepth = sampleViewGuide.w;
	#else
		vec3 sampleViewNormal = normalize(mat3(gbufferModelView) * PtDecodeOctNormal(sampleMeta.xy));
		float sampleDepth = max(sampleMeta.z, 1.0e-3);
	#endif
	#if FOXY_SSPT_ATROUS_SHARED == 0
		sampleViewPosition = SsptAtrousViewPosition(
			samplePixel,
			sampleDepth,
			RtDenoiserMetaPrimaryOffsetIndex(sampleMeta)
		);
	#endif
	vec3 positionDelta = sampleViewPosition - centerViewPosition;
	float planeDistance = max(abs(dot(positionDelta, centerViewNormal)), abs(dot(positionDelta, sampleViewNormal)));
	float radius = sqrt(float(FOXY_SSPT_ATROUS_STEP));
	float planeTolerance = (0.018 + max(centerDepth, sampleDepth) * 0.00055 * (1.0 + radius * 0.45)) * ssptAtrousCenterGrazingRelax;
	float sameSurfaceClass = RtDenoiserSurfaceClassWeight(
		centerSurfaceClass,
		sampleSurfaceClass
	);
	float centerDynamic = 1.0 - step(0.5, abs(centerSurfaceClass - PT_SURFACE_DYNAMIC));
	float sampleDynamic = 1.0 - step(0.5, abs(sampleSurfaceClass - PT_SURFACE_DYNAMIC));
	float classBoundaryWeight = mix(
		1.0,
		sameSurfaceClass,
		max(centerDynamic, sampleDynamic)
	);
	float thinSurface = SsptAtrousThinSurface(centerSurfaceClass) * sameSurfaceClass;
	float physicalNormalAlignment = dot(centerViewNormal, sampleViewNormal);
	float planeExponent = -planeDistance / max(planeTolerance, 1.0e-4);
	float normalAlignment = physicalNormalAlignment;
	if (thinSurface > 0.5) {
		float thinDepthWeight = RtDenoiserRelativeDepthWeight(
			centerDepth,
			sampleDepth,
			0.010,
			0.10
		);
		planeExponent = log2(max(thinDepthWeight, 1.0e-6));
		float envelopeNormalAlignment = dot(
			SsptAtrousThinViewNormal(centerViewPosition),
			SsptAtrousThinViewNormal(sampleViewPosition)
		);
		normalAlignment = max(physicalNormalAlignment, envelopeNormalAlignment);
	} else if (centerDynamic > 0.5) {
		float dynamicDepthWeight = RtDenoiserRelativeDepthWeight(
			centerDepth,
			sampleDepth,
			0.025,
			0.18
		);
		planeExponent = log2(max(dynamicDepthWeight, 1.0e-6));
	}
	float staticNormalWeight = smoothstep(0.84, 0.975, normalAlignment);
	staticNormalWeight *= staticNormalWeight;
	float dynamicNormalWeight = smoothstep(-0.15, 0.80, normalAlignment);
	float normalWeight = mix(staticNormalWeight, dynamicNormalWeight, centerDynamic);
	normalWeight = max(
		normalWeight,
		RtDenoiserGrazingSurfaceFloor(
			sameSurfaceClass,
			ssptAtrousCenterGrazingRelax
		)
	);
	float sampleLuminance = RtDenoiserLuma(sampleSignal.rgb);
	#if FOXY_SSPT_ATROUS_PASS == 0
		float sampleVariance = max(sampleMoments.y - sampleMoments.x * sampleMoments.x, 0.0);
	#else
		float sampleVariance = abs(sampleSignal.a);
	#endif
	float standardDeviation = sqrt(max(centerVariance + sampleVariance, 1.0e-6));
	float luminancePhi = max(
		0.045 + 0.130 * max(centerLuminance, sampleLuminance),
		standardDeviation * mix(3.20, 5.80, Saturate(filterAmount))
	);
	// Product of the plane and luminance guides as one exponent.  This is
	// algebraically identical to two exp2 calls, but costs one transcendental.
	float relativeLuminanceExponent = 0.0;
	float luminanceRejection = mix(1.0, 0.35, centerDynamic) *
		(1.0 - centerFirstPerson);
	#if FOXY_SSPT_ATROUS_PASS == 4
		float relativeLuminanceScale = max(max(centerLuminance, sampleLuminance), 0.002);
		float relativeLuminanceContrast = abs(sampleLuminance - centerLuminance) /
			relativeLuminanceScale;
		float relativeNoise = standardDeviation / relativeLuminanceScale;
		float relativePhi = mix(0.22, 0.85, Saturate(relativeNoise * 0.5));
		relativeLuminanceExponent = -relativeLuminanceContrast / relativePhi *
			luminanceRejection;
	#endif
	float guideWeight = exp2(
		planeExponent - abs(sampleLuminance - centerLuminance) * luminanceRejection /
		max(luminancePhi, 1.0e-4) + relativeLuminanceExponent
	);
	geometrySupportSum += kernelWeight * markerValid * guideWeight * normalWeight * classBoundaryWeight;

	float weight = kernelWeight * filterAmount * markerValid * guideWeight * normalWeight * classBoundaryWeight;
	if (weight <= 1.0e-6) return;
	radianceSum += max(sampleSignal.rgb, vec3(0.0)) * weight;
	varianceNumerator += sampleVariance * weight * weight;
	weightSum += weight;
}

void main() {
	ivec2 filterSize = SsptAtrousOutputSize();
	ssptAtrousFilterSize = filterSize;
	ssptAtrousRenderSize = max(ivec2(SrRenderSize()), ivec2(1));
	ssptAtrousRayToRenderScale = vec2(ssptAtrousRenderSize) /
		vec2(max(filterSize, ivec2(1)));
	vec2 projectionScale = vec2(
		abs(gbufferProjection[0][0]) > 1.0e-6 ? gbufferProjection[0][0] : 1.0,
		abs(gbufferProjection[1][1]) > 1.0e-6 ? gbufferProjection[1][1] : 1.0
	);
	ssptAtrousViewPixelScale = vec2(2.0) /
		(vec2(ssptAtrousRenderSize) * projectionScale);
	#if FOXY_TEMPORAL_JITTER_ACTIVE == 1
		ssptAtrousViewBias = (-vec2(1.0) - temporalJitter) /
			projectionScale;
	#else
		ssptAtrousViewBias = -vec2(1.0) / projectionScale;
	#endif
	#if FOXY_SSPT_ATROUS_SHARED == 1
		SsptAtrousPreload(filterSize);
	#endif

	ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
	if (any(greaterThanEqual(pixel, filterSize))) return;
	vec4 centerSignal;
	vec4 centerViewGuide;
	vec3 centerViewPosition;
	float centerAge;
	float centerSurfaceClass;
	float centerValid;
	float centerFirstPerson = 0.0;
	#if FOXY_SSPT_ATROUS_SHARED == 0
		vec4 centerMeta;
	#endif
	#if FOXY_SSPT_ATROUS_PASS == 0
		vec4 centerMoments;
	#endif
	ivec2 centerLocalPixel = ivec2(gl_LocalInvocationID.xy) + ivec2(SSPT_FILTER_RADIUS);
	#if FOXY_SSPT_ATROUS_SHARED == 1
		int centerIndex = SsptAtrousSharedIndex(centerLocalPixel);
		centerSignal = ssptSharedSignal[centerIndex];
		centerViewGuide = ssptSharedViewGuide[centerIndex];
		centerViewPosition = vec3(
			ssptSharedViewPositionX[centerIndex],
			ssptSharedViewPositionY[centerIndex],
			ssptSharedViewPositionZ[centerIndex]
		);
		#if FOXY_SSPT_ATROUS_PASS == 0
			centerMoments = ssptSharedMoments[centerIndex];
			centerAge = centerSignal.a;
		#else
			centerAge = ssptSharedMetaAge[centerIndex];
		#endif
		centerSurfaceClass = ssptSharedMetaSurfaceClass[centerIndex];
		centerValid = step(0.5, ssptSharedMetaAge[centerIndex]);
		centerFirstPerson = ssptSharedFirstPerson[centerIndex];
	#else
		centerSignal = SsptAtrousSource(pixel);
		centerMeta = SsptAtrousMeta(pixel);
		#if FOXY_SSPT_ATROUS_PASS == 0
			centerMoments = SsptAtrousMoments(pixel);
			centerAge = centerSignal.a;
		#else
			centerAge = RtDenoiserMetaAge(centerMeta);
		#endif
		centerSurfaceClass = RtDenoiserMetaSurfaceClass(centerMeta);
		centerValid = RtDenoiserMetaValid(centerMeta);
	#endif

	centerValid *= step(0.5, centerAge);
	#if FOXY_SSPT_ATROUS_PASS == 0
		#if FOXY_SSPT_ATROUS_SHARED == 1
			bool centerFinite = RtDenoiserFinite4(centerSignal)
				&& RtDenoiserFinite4(centerMoments);
		#else
			bool centerFinite = RtDenoiserFinite4(centerSignal)
				&& RtDenoiserFinite4(centerMeta)
				&& RtDenoiserFinite4(centerMoments);
		#endif
	#else
		#if FOXY_SSPT_ATROUS_SHARED == 1
			bool centerFinite = true;
		#else
			bool centerFinite = RtDenoiserFinite4(centerMeta);
		#endif
	#endif
	if (centerValid < 0.5 || !centerFinite) {
		SsptAtrousStore(pixel, vec4(0.0));
		return;
	}
	#if FOXY_SSPT_ATROUS_SHARED == 0
		centerFirstPerson = SsptAtrousFirstPerson(pixel, centerMeta);
	#endif

	#if FOXY_SSPT_ATROUS_SHARED == 1
		vec3 centerViewNormal = centerViewGuide.xyz;
		float centerDepth = centerViewGuide.w;
	#else
		vec3 centerViewNormal = normalize(mat3(gbufferModelView) * PtDecodeOctNormal(centerMeta.xy));
		float centerDepth = max(centerMeta.z, 1.0e-3);
	#endif
	#if FOXY_SSPT_ATROUS_SHARED == 0
		centerViewPosition = SsptAtrousViewPosition(
			pixel,
			centerDepth,
			RtDenoiserMetaPrimaryOffsetIndex(centerMeta)
		);
	#endif
	ssptAtrousCenterGrazingRelax = mix(
		2.20,
		1.0,
		sqrt(Saturate(abs(centerViewNormal.z)))
	);
	float centerLuminance = RtDenoiserLuma(centerSignal.rgb);
	#if FOXY_SSPT_ATROUS_PASS == 0
		float centerVariance = max(centerMoments.y - centerMoments.x * centerMoments.x, 0.0);
	#else
		float centerVariance = abs(centerSignal.a);
	#endif
	float relativeDeviation = sqrt(centerVariance) / max(centerLuminance + 0.04, 0.04);
	float startupUncertainty = 1.0 - Saturate(centerAge * 0.25);
	float uncertainty = Saturate(relativeDeviation * 0.66 + startupUncertainty * 0.78);
	#if FOXY_SSPT_ATROUS_PASS == 0
		float passAmount = mix(0.55, 1.25, uncertainty);
	#elif FOXY_SSPT_ATROUS_PASS == 1
		float passAmount = mix(0.42, 1.12, uncertainty);
	#else
		float passAmount = mix(0.70, 1.00, uncertainty);
	#endif
	float filterAmount = min(clamp(FOXY_SSPT_DENOISE_STRENGTH, 0.0, 2.0) * passAmount, 1.6);

	const float centerKernelWeight = 0.25;
	vec3 radianceSum = max(centerSignal.rgb, vec3(0.0)) * centerKernelWeight;
	float varianceNumerator = centerVariance * centerKernelWeight * centerKernelWeight;
	float weightSum = centerKernelWeight;
	float geometrySupportSum = 0.0;
	ivec2 sampleShift = ivec2(0);
	#if FOXY_SSPT_ATROUS_PASS > 0 && !defined(FOXY_SSPT_ATROUS_DENSE)
		bool stablePlanar = centerSignal.a >= 0.0;
		bool insideStableBorder = all(greaterThanEqual(pixel, ivec2(FOXY_SSPT_ATROUS_STEP)))
			&& all(lessThan(pixel, filterSize - ivec2(FOXY_SSPT_ATROUS_STEP)));
		if (stablePlanar && insideStableBorder) {
			float passValue = float(FOXY_SSPT_ATROUS_PASS);
			float jitterX = Hash12(vec2(pixel) + vec2(passValue * 17.31, 9.73));
			float jitterY = Hash12(vec2(pixel.y, pixel.x) + vec2(passValue * 23.17, 5.19));
			sampleShift = ivec2(round((vec2(jitterX, jitterY) - 0.5) * float(FOXY_SSPT_ATROUS_STEP)));
		}
	#endif
	#if SSPT_FILTER_PATTERN != 2
		SsptAtrousTap(pixel, centerLocalPixel, ivec2(-1,  0), sampleShift, SSPT_AXIS_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2( 1,  0), sampleShift, SSPT_AXIS_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2( 0, -1), sampleShift, SSPT_AXIS_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2( 0,  1), sampleShift, SSPT_AXIS_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
	#endif
	#if SSPT_FILTER_PATTERN != 1
		SsptAtrousTap(pixel, centerLocalPixel, ivec2(-1, -1), sampleShift, SSPT_DIAGONAL_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2( 1, -1), sampleShift, SSPT_DIAGONAL_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2(-1,  1), sampleShift, SSPT_DIAGONAL_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
		SsptAtrousTap(pixel, centerLocalPixel, ivec2( 1,  1), sampleShift, SSPT_DIAGONAL_WEIGHT, centerViewNormal, centerViewPosition, centerDepth, centerSurfaceClass, centerLuminance, centerVariance, centerFirstPerson, filterAmount, radianceSum, varianceNumerator, weightSum, geometrySupportSum);
	#endif

	vec3 filteredRadiance = radianceSum / max(weightSum, 1.0e-5);
	float filteredVariance = varianceNumerator / max(weightSum * weightSum, 1.0e-5);
	float stablePlanarMarker;
	#if FOXY_SSPT_ATROUS_PASS == 0
		stablePlanarMarker = centerSurfaceClass < 0.5 && geometrySupportSum / 0.75 >= 0.92 ? 1.0 : 0.0;
	#else
		stablePlanarMarker = centerSignal.a >= 0.0 ? 1.0 : 0.0;
	#endif
	float packedVariance = stablePlanarMarker > 0.5 ? filteredVariance : -filteredVariance - 1.0e-6;
	SsptAtrousStore(
		pixel,
		vec4(
			min(max(filteredRadiance, vec3(0.0)), vec3(64.0)),
			clamp(packedVariance, -4096.0, 4096.0)
		)
	);
}

#undef SSPT_FILTER_GROUP_SIZE
#undef SSPT_FILTER_JITTER_RADIUS
#undef SSPT_FILTER_RADIUS
#undef SSPT_FILTER_TILE_SIDE
#undef SSPT_FILTER_TILE_AREA
#undef SSPT_FILTER_PATTERN
#undef SSPT_AXIS_WEIGHT
#undef SSPT_DIAGONAL_WEIGHT
