Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 42 additions & 30 deletions package/Shaders/ISTemporalAA.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "Common/DisplayMapping.hlsli"
#include "Common/DummyVSTexCoord.hlsl"
#include "Common/FrameBuffer.hlsli"
#include "Common/VR.hlsli"

typedef VS_OUTPUT PS_INPUT;

Expand Down Expand Up @@ -73,6 +74,12 @@ float DecodeFeedbackLuma(float gammaLuma)

static const float3 kLumaWeights = float3(0.5, 0.25, 0.25);

// Named magic constants from the vanilla decompile (values unchanged).
static const float kMaxLumaCap = 1.00100005; // bracket ceiling (just above 1.0 PQ)
static const float kMinLumaCap = -0.00100000005; // bracket floor (just below 0)
static const float kLumaEpsilon = 0.00999999978; // negligible-luma threshold
static const float2 kSimilarityScale = float2(20, 100); // motion-diff convergence decay (x), strict (y)

/*
* Channel layout (vanilla decompile — swizzles are load-bearing):
* - Neighbour taps: .yxz sample; luma via dot(.xzy, kLumaWeights); stored as float4(.xyz=GRB, .w=luma).
Expand Down Expand Up @@ -264,9 +271,17 @@ PS_OUTPUT main(PS_INPUT input)
// --- motion vector and history sample ---
history.xy = drMax;
motionReject.xy = velocityTex.Sample(velocitySampler, ClampScreenUV(motionReject.xy, history.xy)).xy;
# ifdef VR
// Eyes share one RT in VR; reproject within the current eye (mono-space velocity, per-eye
// clamp) so a pixel near the x=0.5 seam never samples the other eye's history. OOB feeds reject.
bool prevUVOutOfBounds;
float2 prevUV = Stereo::ApplyVelocityToUV(texCoord.xy, motionReject.xy, prevUVOutOfBounds);
tapMin.xy = FrameBuffer::GetPreviousDynamicResolutionAdjustedScreenPosition(prevUV);
# else
motionReject.zw = texCoord.xy + motionReject.xy;
motionReject.x = sqrt(dot(motionReject.xy, motionReject.xy));
tapMin.xy = ClampHistoryUV(motionReject.zw);
# endif
motionReject.x = sqrt(dot(motionReject.xy, motionReject.xy));
history.xyw = historyTex.Sample(historySampler, tapMin.xy).xyz;
# ifdef HDR_OUTPUT
// history.x is stored as game-gamma luma (see EncodeFeedbackLuma on write).
Expand Down Expand Up @@ -302,9 +317,9 @@ PS_OUTPUT main(PS_INPUT input)
// Bracket ceiling: 1.001 is just above the maximum PQ value (1.0 = 10000 nits).
// Nothing in the scene exceeds this, so the ceiling works correctly in PQ working space.
// (In linear BT2020 this would be wrong — sky/specular exceed 1.0 linear — but PQ is bounded.)
bracketMax.y = cmp(centerMeta.x < 1.00100005);
bracketMax.yzw = bracketMax.yyy ? centerMeta.yzx : float3(1.00100005, 1.00100005, 1.00100005);
bracketMax.yzw = bracketMax.xxx ? float3(1.00100005, 1.00100005, 1.00100005) : bracketMax.yzw;
bracketMax.y = cmp(centerMeta.x < kMaxLumaCap);
bracketMax.yzw = bracketMax.yyy ? centerMeta.yzx : kMaxLumaCap.xxx;
bracketMax.yzw = bracketMax.xxx ? kMaxLumaCap.xxx : bracketMax.yzw;

weightedColor.x = cmp(tapC1.w < bracketMax.w);
weightedColor.xyz = weightedColor.xxx ? tapC1.yzw : bracketMax.yzw;
Expand Down Expand Up @@ -335,9 +350,9 @@ PS_OUTPUT main(PS_INPUT input)
mergeScratch.yzw = sampleUV.www ? bracketMax.yzw : mergeScratch.xyz;
tapB1.x = cmp(corner.w < mergeScratch.w);
bracketMinReg.yzw = tapB1.xxx ? corner.yzw : mergeScratch.yzw;
tapB1.x = cmp(-0.00100000005 < centerMeta.x);
bracketMax.yzw = tapB1.xxx ? centerMeta.yzx : float3(-0.00100000005, -0.00100000005, -0.00100000005);
bracketMax.xyz = bracketMax.xxx ? bracketMax.yzw : float3(-0.00100000005, -0.00100000005, -0.00100000005);
tapB1.x = cmp(kMinLumaCap < centerMeta.x);
bracketMax.yzw = tapB1.xxx ? centerMeta.yzx : kMinLumaCap.xxx;
bracketMax.xyz = bracketMax.xxx ? bracketMax.yzw : kMinLumaCap.xxx;
tapB1.x = cmp(bracketMax.z < tapC1.w);
tapC1.xyz = tapB1.xxx ? tapC1.yzw : bracketMax.xyz;
tapC1.xyz = center.xxx ? tapC1.xyz : bracketMax.xyz;
Expand Down Expand Up @@ -409,7 +424,7 @@ PS_OUTPUT main(PS_INPUT input)
sampleUV.w = cmp(motionReject.y < 0.902499974);
history.xyz = sampleUV.www ? tapA1.xyz : tapB0.xyz;
history.yz = history.zx + -history.yy;
corner.w = cmp(0.00999999978 < history.y);
corner.w = cmp(kLumaEpsilon < history.y);
history.y = history.z / history.y;
history.y = corner.w ? history.y : 0.5;
tapMin.xyz = sampleUV.www ? tapMin.xyz : tapC0.xyz;
Expand All @@ -418,12 +433,17 @@ PS_OUTPUT main(PS_INPUT input)
corner.xyz = history.yyy * corner.xyz + tapMin.xyz;

// --- disocclusion / mask rejection ---
// motionReject.zw still holds reprojected UV from motion pass; motionReject.x = motion length
// motionReject.x = motion length (motionReject.zw held the reprojected UV on the SE path).
# ifdef VR
// VR resolves out-of-bounds per-eye in mono space; the SE stereo-space test misses the seam.
motionReject.z = prevUVOutOfBounds ? 1 : 0;
# else
sampleUV.w = min(motionReject.z, motionReject.w);
motionReject.zw = cmp(motionReject.zw >= float2(1, 1));
sampleUV.w = cmp(0 >= sampleUV.w);
motionReject.z = (int)motionReject.z | (int)sampleUV.w;
motionReject.z = (int)motionReject.w | (int)motionReject.z;
# endif
history.yz = maskTex.Sample(maskSampler, sampleUV.xy).xy;
motionReject.w = AlphaCoverageMask(sampleUV.xy);
sampleUV.x = cmp(ThresholdParams.w < history.z);
Expand All @@ -445,11 +465,11 @@ PS_OUTPUT main(PS_INPUT input)
motionReject.z = history.x + -centerMeta.x;
{
float lumaDiffScaled = abs(motionReject.z) * 0.05;
history.xw = -lumaDiffScaled.xx * float2(20, 100) + float2(1, 1);
history.xw = -lumaDiffScaled.xx * kSimilarityScale + float2(1, 1);
}
# else
motionReject.z = history.x + -centerMeta.x;
history.xw = -abs(motionReject.xx) * float2(20, 100) + float2(1, 1);
history.xw = -abs(motionReject.xx) * kSimilarityScale + float2(1, 1);
# endif
history.xw = max(float2(0, 0), history.xw);
tapMin.yzw = history.xxx * tapMin.xyz + corner.xyz;
Expand Down Expand Up @@ -479,25 +499,20 @@ PS_OUTPUT main(PS_INPUT input)

motionReject.y = motionReject.x * motionReject.z + centerMeta.x;
motionReject.x = motionReject.x * motionReject.z;
motionReject.x = cmp(abs(motionReject.x) < 0.00999999978);
motionReject.x = cmp(abs(motionReject.x) < kLumaEpsilon);
corner.x = motionReject.x ? centerMeta.x : motionReject.y;
tapMin.x = dot(tapMin.zwy, kLumaWeights);

// --- alpha-aware output ---
motionReject.x = AlphaCoverageMask(drNeighborsA.xy);
motionReject.y = AlphaCoverageMask(drNeighborsA.zw);
motionReject.x = motionReject.x ? sampleUV.z : 0;
motionReject.x = motionReject.y ? motionReject.x : 0;
motionReject.y = AlphaCoverageMask(drNeighborsB.xy);
motionReject.z = AlphaCoverageMask(drNeighborsB.zw);
motionReject.x = motionReject.y ? motionReject.x : 0;
motionReject.x = motionReject.z ? motionReject.x : 0;
motionReject.y = AlphaCoverageMask(drNeighborsC.xy);
motionReject.z = AlphaCoverageMask(drNeighborsC.zw);
motionReject.x = motionReject.y ? motionReject.x : 0;
motionReject.x = motionReject.z ? motionReject.x : 0;
motionReject.y = AlphaCoverageMask(drUVMax);
motionReject.x = motionReject.y ? motionReject.x : 0;
// allTransparent: every 3x3 tap covered by alpha. Seed = uvMin coverage (sampleUV.z),
// gated by the 8 remaining taps (motionReject.w already holds the centre tap's coverage).
const float2 alphaUVs[7] = {
drNeighborsA.xy, drNeighborsA.zw, drNeighborsB.xy, drNeighborsB.zw,
drNeighborsC.xy, drNeighborsC.zw, drUVMax
};
motionReject.x = sampleUV.z;
[unroll] for (int alphaTap = 0; alphaTap < 7; alphaTap++)
motionReject.x = AlphaCoverageMask(alphaUVs[alphaTap]) ? motionReject.x : 0;
motionReject.x = motionReject.w ? motionReject.x : 0;
motionReject.y = cmp(ThresholdParams.w >= history.y);
motionReject.z = 1 + -history.z;
Expand All @@ -513,11 +528,8 @@ PS_OUTPUT main(PS_INPUT input)
# else
feedbackOut.x = saturate(sampleUV.x * motionReject.z);
# endif
# if defined(VR)
colorOut.w = motionReject.x ? 1 : 0;
# else
// Vanilla writes opaque alpha unconditionally on both SE and VR (decompile o0.w = 1).
colorOut.w = 1;
# endif
feedbackOut.w = 1;

# ifdef HDR_OUTPUT
Expand Down
Loading