591 lines
25 KiB
HLSL
591 lines
25 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
|
|
//------------------------------------------------------- INCLUDES
|
|
|
|
#include "TSRColorSpace.ush"
|
|
#include "TSRSpatialAntiAliasing.ush"
|
|
#include "TSRConvolutionNetworkPass.ush"
|
|
|
|
//------------------------------------------------------- CONFIG
|
|
|
|
#ifndef CONFIG_THIN_GEOMETRY_DETECTION
|
|
#define CONFIG_THIN_GEOMETRY_DETECTION 0
|
|
#endif
|
|
|
|
//------------------------------------------------------- CONSTANTS
|
|
|
|
static const float kHistoryGuidePreceptionAdd = 1.0;
|
|
|
|
static const tsr_half FilteringWeight = rcp(tsr_half(1.0 + 4 * 0.5 + 4 * 0.25));
|
|
|
|
|
|
//------------------------------------------------------- PARAMETERS
|
|
|
|
float FlickeringFramePeriod;
|
|
uint bPassthroughAlpha;
|
|
|
|
//------------------------------------------------------- COMPOSE SEPARATE TRANSLUCENCY
|
|
|
|
/** Compose translucency in linear color space. */
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC ComposeTranslucency(tsr_tensor_halfC Color, tsr_tensor_half4 Translucency, tsr_tensor_bool bAllowPassthroughAlpha = tsr_tensor_bool::Const(true))
|
|
{
|
|
tsr_tensor_halfC ComposedColor;
|
|
ComposedColor = Color * tsr_tensor_halfC::Vectorize(Translucency[3]) + ResizeChannels<CONFIG_CHANNEL_COUNT>(Translucency);
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
ComposedColor.SetComponent(3, select(and(tsr_tensor_bool::Const(bPassthroughAlpha > 0), bAllowPassthroughAlpha), Color[3], Color[3] * Translucency[3]));
|
|
#endif
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
ComposedColor = min(ComposedColor, tsr_tensor_halfC::Const(LargestSceneColorRGBA));
|
|
#else
|
|
ComposedColor = min(ComposedColor, tsr_tensor_halfC::Const(LargestSceneColorRGB));
|
|
#endif
|
|
return ComposedColor;
|
|
}
|
|
|
|
/** Compose blurry translucency for tighter rejection in linear color space. */
|
|
tsr_tensor_halfC ComposeTranslucencyForRejection(tsr_tensor_halfC OriginalOpaqueInput, tsr_tensor_half4 OriginalTranslucencyInput, tsr_tensor_bool bHasPixelAnimation)
|
|
{
|
|
tsr_tensor_halfC SharpInput = OriginalOpaqueInput;
|
|
tsr_tensor_half4 BlurInput = OriginalTranslucencyInput;
|
|
|
|
{
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
const tsr_half4 TransparentSharpInput = tsr_half4(0.0, 0.0, 0.0, 1.0);
|
|
#else
|
|
const tsr_half3 TransparentSharpInput = tsr_half(0.0).xxx;
|
|
#endif
|
|
|
|
tsr_tensor_halfC CenterFinalSceneColor = ComposeTranslucency(OriginalOpaqueInput, OriginalTranslucencyInput);
|
|
|
|
tsr_tensor_half4 HasAnimationBlurInput = ResizeChannels<4>(CenterFinalSceneColor);
|
|
|
|
SharpInput = select(tsr_tensor_boolC::Vectorize(bHasPixelAnimation), tsr_tensor_halfC::Const(TransparentSharpInput), SharpInput);
|
|
BlurInput = select(tsr_tensor_bool4::Vectorize(bHasPixelAnimation), HasAnimationBlurInput, BlurInput);
|
|
}
|
|
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
// Disallow alpha passthrough on opaque materials with pixel animation to retain scene color alpha values (i.e. dynamic holdout state).
|
|
const tsr_tensor_bool bAllowPassthroughAlpha = not(bHasPixelAnimation);
|
|
|
|
return ComposeTranslucency(SharpInput, Blur3x3(BlurInput), bAllowPassthroughAlpha);
|
|
#else
|
|
return ComposeTranslucency(SharpInput, Blur3x3(BlurInput));
|
|
#endif
|
|
}
|
|
|
|
|
|
//------------------------------------------------------- CHANGE LDR EXPOSURE
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC RemovePerceptionAdd(tsr_tensor_halfC SrcColor, const float SourcePerceptionAdd)
|
|
{
|
|
tsr_tensor_halfC Minus = tsr_tensor_halfC::Const(tsr_half(1.0)) - SrcColor;
|
|
tsr_tensor_halfC LDRToLinear = min(tsr_tensor_halfC::Const(tsr_half(SourcePerceptionAdd)) * rcp(Minus), tsr_tensor_halfC::Const(tsr_half(MaxHalfFloat)));
|
|
|
|
return SrcColor * LDRToLinear;
|
|
}
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC AddPerceptionAdd(tsr_tensor_halfC SrcColor, const float DestPerceptionAdd)
|
|
{
|
|
SrcColor = SrcColor * rcp(SrcColor + tsr_tensor_halfC::Const(tsr_half(DestPerceptionAdd)));
|
|
return SrcColor;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------- GUIDE COLOR SPACE
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC LinearToGCS(tsr_tensor_halfC SrcColor)
|
|
{
|
|
tsr_tensor_halfC GColor = SrcColor * rcp(SrcColor + tsr_tensor_halfC::Const(tsr_half(0.17)));
|
|
//SMColor = SMColor * SMColor;
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
GColor.SetComponent(3, SrcColor[3]);
|
|
#endif
|
|
return GColor;
|
|
}
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC GCSToLinear(tsr_tensor_halfC GColor)
|
|
{
|
|
//SrcColor = sqrt(SrcColor);
|
|
|
|
tsr_tensor_halfC Minus = tsr_tensor_halfC::Const(tsr_half(1.0)) - GColor;
|
|
tsr_tensor_halfC LDRToLinear = min(tsr_tensor_halfC::Const(tsr_half(0.17)) * rcp(Minus), tsr_tensor_halfC::Const(tsr_half(MaxHalfFloat)));
|
|
|
|
tsr_tensor_halfC LinearColor = GColor * LDRToLinear;
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
LinearColor.SetComponent(3, GColor[3]);
|
|
#endif
|
|
return LinearColor;
|
|
}
|
|
|
|
//------------------------------------------------------- SHADING MEASUREMENT COLOR SPACE
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_half GCSToSMCS(tsr_tensor_half GColor)
|
|
{
|
|
tsr_tensor_half SMColor = GColor * GColor;
|
|
return SMColor;
|
|
}
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_half SMCSToGCS(tsr_tensor_half SMColor)
|
|
{
|
|
tsr_tensor_half GColor = sqrt(SMColor);
|
|
return GColor;
|
|
}
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC GCSToSMCS(tsr_tensor_halfC GColor)
|
|
{
|
|
tsr_tensor_halfC SMColor = GColor * GColor;
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
SMColor.SetComponent(3, GColor[3]);
|
|
#endif
|
|
return SMColor;
|
|
}
|
|
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_halfC LinearToSMCS(tsr_tensor_halfC LinearColor)
|
|
{
|
|
return GCSToSMCS(LinearToGCS(LinearColor));
|
|
}
|
|
|
|
|
|
//------------------------------------------------------- SPATIAL ANTI-ALIASER
|
|
|
|
// Compute the LDR luminance the spatial anti-aliaser should anti-alias.
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_half ComputeSpatialAntiAliaserLumaLDR(tsr_tensor_halfC SceneColor)
|
|
{
|
|
const tsr_half SpatialAAExposure = tsr_half(0.5);
|
|
|
|
tsr_tensor_half AALumaLDR;
|
|
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
tsr_half PixelLuma = dot(SceneColor.GetElement(ElementIndex).rgb, tsr_half3(0.299f, 0.587f, 0.114f));
|
|
AALumaLDR.SetElement(ElementIndex, PixelLuma / (SpatialAAExposure + PixelLuma));
|
|
}
|
|
|
|
return AALumaLDR;
|
|
}
|
|
|
|
// Returns whether the aliasing is visible at all, to avoid paying spatial-antialiaser on low contrasts areas.
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_bool IsAliasingVisible(tsr_tensor_halfC ExposedInputBoxSize)
|
|
{
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
tsr_tensor_half ExposedInputLuminanceBox = dot(ExposedInputBoxSize, tsr_tensor_halfC::Const(tsr_halfC(0.299f, 0.587f, 0.114f, 0.0)));
|
|
#else
|
|
tsr_tensor_half ExposedInputLuminanceBox = dot(ExposedInputBoxSize, tsr_tensor_halfC::Const(tsr_halfC(0.299f, 0.587f, 0.114f)));
|
|
#endif
|
|
|
|
tsr_tensor_bool bAliasingIsVisible = ExposedInputLuminanceBox > tsr_tensor_half::Const(SPATIAL_ANTI_ALIASER_MIN_LUMIMANCE);
|
|
|
|
return bAliasingIsVisible;
|
|
}
|
|
|
|
// Whether to run spatial anti-aliaser on the frame. Must match SpatialAntiAliasingLerp.
|
|
CALL_SITE_DEBUGLOC
|
|
tsr_tensor_bool ShouldSpatialAntiAlias(tsr_tensor_bool bIsDisoccluded, tsr_tensor_bool bResurrectHistory, tsr_tensor_bool bAliasingIsVisible, tsr_tensor_half RejectionBlendFinal)
|
|
{
|
|
tsr_tensor_bool bSpatialAntiAlias = and(bAliasingIsVisible, or(RejectionBlendFinal < tsr_tensor_half::Const(tsr_half(0.25)), and(bIsDisoccluded, not(bResurrectHistory))));
|
|
|
|
return bSpatialAntiAlias;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------- FLICKERING TEMPORAL ANALYSIS
|
|
|
|
void ComputeMoireError(
|
|
tsr_tensor_bool bIsDisoccluded,
|
|
tsr_tensor_half bIsStatic,
|
|
tsr_tensor_half MoireInput,
|
|
tsr_tensor_halfC OriginalOpaqueInput,
|
|
tsr_tensor_half4 OriginalTranslucencyInput,
|
|
tsr_tensor_half4 PrevMoireHistory,
|
|
out tsr_tensor_half4 OutMoireHistory,
|
|
out tsr_tensor_half OutMoireError)
|
|
{
|
|
const tsr_half MinBlendFinal = tsr_half(0.05);
|
|
const tsr_half MoireEncodingError = tsr_half(rcp(127.0));
|
|
const tsr_half MaxPrevTotalVariationCount = tsr_half(20.0);
|
|
|
|
// Compute the threshold at which the variation count should quick in
|
|
const tsr_half TotalVariationCountThreshold = tsr_half(1.0 / (1.0 - pow(1.0 - MinBlendFinal, tsr_half(FlickeringFramePeriod))));
|
|
|
|
// Input luminance to monitor flickering from.
|
|
tsr_tensor_half InputLuma = GCSToSMCS(MoireInput);
|
|
|
|
// How much input luminance from the TSRComputeMoireLuma.usf measurement has been modified since for instance with fog, water.
|
|
tsr_tensor_half InputLumaModification;
|
|
{
|
|
const tsr_half InputLumaEncodingError = tsr_half(0.5 / 255.0);
|
|
|
|
tsr_tensor_halfC OriginalInput = ComposeTranslucency(OriginalOpaqueInput, OriginalTranslucencyInput);
|
|
|
|
OriginalInput = LinearToSMCS(OriginalInput);
|
|
OriginalOpaqueInput = LinearToSMCS(OriginalOpaqueInput);
|
|
|
|
tsr_tensor_half SceneColorLuma;
|
|
tsr_tensor_half TranslucencyChange;
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
SceneColorLuma.SetElement(ElementIndex, dot(OriginalOpaqueInput.GetElement(ElementIndex).rgb, kMoireSMCSWeights));
|
|
TranslucencyChange.SetElement(ElementIndex, dot(abs(OriginalOpaqueInput.GetElement(ElementIndex).rgb - OriginalInput.GetElement(ElementIndex).rgb), kMoireSMCSWeights));
|
|
}
|
|
|
|
InputLumaModification = tsr_tensor_half::Const(0.0);
|
|
InputLumaModification = max(InputLumaModification, abs(InputLuma - SceneColorLuma) - tsr_tensor_half::Const(InputLumaEncodingError));
|
|
InputLumaModification = max(InputLumaModification, TranslucencyChange);
|
|
InputLumaModification = max(InputLumaModification, tsr_tensor_half::Const(1.0) - OriginalTranslucencyInput[3]);
|
|
}
|
|
|
|
// Unpack history
|
|
tsr_tensor_half PrevFlickeringHistory = GCSToSMCS(PrevMoireHistory[0]);
|
|
tsr_tensor_half PrevGradient = PrevMoireHistory[1] * tsr_half(255.0 / 127.0) - tsr_half(1.0);
|
|
//tsr_tensor_half PrevTotalVariation = PrevMoireHistory[2] * tsr_tensor_half::Const(1.0 - MinBlendFinal);
|
|
tsr_tensor_half PrevTotalVariation = PrevMoireHistory[2] * tsr_tensor_half::Const(tsr_half(1.0) - MinBlendFinal);
|
|
tsr_tensor_half PrevTotalVariationCount = PrevMoireHistory[3] * MaxPrevTotalVariationCount * tsr_tensor_half::Const(tsr_half(1.0) - MinBlendFinal);
|
|
|
|
// Discard history on moving objects.
|
|
PrevTotalVariation = PrevTotalVariation * bIsStatic;
|
|
PrevTotalVariationCount = PrevTotalVariationCount * bIsStatic;
|
|
|
|
// Run the exact same heuristic as MeasureRejection on a single channel.
|
|
tsr_tensor_half FilteredInputLumaModification;
|
|
tsr_tensor_half OutRejectionBlendFinal;
|
|
tsr_tensor_half OutRejectionClampBlend;
|
|
{
|
|
tsr_tensor_half BackbufferQuantizationErrorVector = tsr_tensor_half::Const(MeasureBackbufferLDRQuantizationError());
|
|
|
|
tsr_tensor_half InputC0 = InputLuma;
|
|
tsr_tensor_half HistoryC0 = PrevFlickeringHistory;
|
|
|
|
// Anhilate history
|
|
tsr_tensor_half InputC2 = InputC0;
|
|
tsr_tensor_half HistoryC2 = HistoryC0;
|
|
AnnihilateMutuallySingleChannel3x3(InputLuma, HistoryC0, /* out */ InputC2, /* out */ HistoryC2);
|
|
|
|
// Convolve the input luma to have the exact same moire pattern as FilteredInput below.
|
|
tsr_tensor_half FilteredInput;
|
|
tsr_tensor_half FilteredHistory;
|
|
Deconcatenate(Blur3x3(Concatenate(InputC2, InputLumaModification, HistoryC2)), /* out */ FilteredInput, /* out */ FilteredInputLumaModification, /* out */ FilteredHistory);
|
|
|
|
// Compute clamping box of the limance.
|
|
tsr_tensor_half FilteredBoxMin;
|
|
tsr_tensor_half FilteredBoxMax;
|
|
tsr_tensor_half InputC2BoxSize;
|
|
{
|
|
tsr_tensor_half2 Min;
|
|
tsr_tensor_half2 Max;
|
|
MinMax3x3(Concatenate(FilteredInput, InputC2), /* out */ Min, /* out */ Max);
|
|
FilteredBoxMin = Min[0];
|
|
FilteredBoxMax = Max[0];
|
|
|
|
InputC2BoxSize = Max[1] - Min[1];
|
|
}
|
|
|
|
// Compute the clamp error
|
|
tsr_tensor_half ClampError;
|
|
{
|
|
tsr_tensor_half TotalVarInputDiffC0C2;
|
|
tsr_tensor_half TotalVarInputC2;
|
|
Deconcatenate(abs(TotalVariation3x3(Concatenate(abs(InputC0 - InputC2), InputC2))), /* out */ TotalVarInputDiffC0C2, /* out */ TotalVarInputC2);
|
|
|
|
ClampError = BackbufferQuantizationErrorVector;
|
|
ClampError = max(ClampError, Blur3x3(min(TotalVarInputDiffC0C2, TotalVarInputC2)));
|
|
ClampError = max(ClampError, InputC2BoxSize * tsr_half(FilteringWeight * 0.25));
|
|
ClampError = ClampError + BackbufferQuantizationErrorVector;
|
|
}
|
|
|
|
// Apply clamp error
|
|
FilteredBoxMin = FilteredBoxMin - ClampError;
|
|
FilteredBoxMax = FilteredBoxMax + ClampError;
|
|
|
|
tsr_tensor_half BoxSize = (
|
|
InputC2BoxSize * tsr_tensor_half::Const(FilteringWeight) +
|
|
(BackbufferQuantizationErrorVector * tsr_half(2 * FilteringWeight)));
|
|
|
|
tsr_tensor_half ClampedFilteredHistory = fastClamp(FilteredHistory, FilteredBoxMin, FilteredBoxMax);
|
|
|
|
tsr_tensor_half Delta = max(abs(FilteredInput - FilteredHistory), BoxSize);
|
|
|
|
tsr_tensor_half RawClampedEnergy = abs(ClampedFilteredHistory - FilteredHistory);
|
|
tsr_tensor_half RawRejection = saturate(tsr_tensor_half::Const(1.0) - max(RawClampedEnergy, tsr_tensor_half::Const(0.0)) * rcp(Delta));
|
|
|
|
tsr_tensor_half FilteredClampedEnergy;
|
|
Deconcatenate(MaxRMinG3x3(Median3x3(Concatenate(RawClampedEnergy, RawRejection))), /* out */ FilteredClampedEnergy, /* out */ OutRejectionClampBlend);
|
|
|
|
OutRejectionBlendFinal = saturate(tsr_tensor_half::Const(1.0) - max(FilteredClampedEnergy, tsr_tensor_half::Const(0.0)) * rcp(Delta));
|
|
}
|
|
|
|
// Simulate what happens in TSRUpdateHistory.usf
|
|
tsr_tensor_half FinalFlickeringHistory;
|
|
tsr_tensor_half CurrentGradient;
|
|
{
|
|
tsr_tensor_half ClampedPrevFlickeringHistory = ClampPlus3x3(PrevFlickeringHistory, InputLuma);
|
|
tsr_tensor_half FinalPrevFlickeringHistory = lerp(ClampedPrevFlickeringHistory, PrevFlickeringHistory, OutRejectionClampBlend);
|
|
|
|
tsr_tensor_half FinalBlendFinal = max(tsr_tensor_half::Const(1.0) - OutRejectionBlendFinal, tsr_tensor_half::Const(MinBlendFinal));
|
|
|
|
FinalFlickeringHistory = InputLuma * FinalBlendFinal - FinalPrevFlickeringHistory * (FinalBlendFinal - tsr_tensor_half::Const(1.0));
|
|
|
|
tsr_tensor_half GhostingHistory = InputLuma * tsr_tensor_half::Const(MinBlendFinal) - PrevFlickeringHistory * tsr_tensor_half::Const(MinBlendFinal - (1.0));
|
|
|
|
CurrentGradient = FinalFlickeringHistory - GhostingHistory;
|
|
}
|
|
|
|
FinalFlickeringHistory = SMCSToGCS(FinalFlickeringHistory);
|
|
|
|
// Remove how much luma has changed from luma measurement to final scene color from the gradient.
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
tsr_half LocalCurrentGradient = CurrentGradient.GetElement(ElementIndex);
|
|
tsr_half LocalInputLumaModification = FilteredInputLumaModification.GetElement(ElementIndex);
|
|
|
|
CurrentGradient.SetElement(ElementIndex, clamp(LocalCurrentGradient * tsr_half(POSITIVE_INFINITY), tsr_half(-1.0), tsr_half(1.0)) * max(abs(LocalCurrentGradient) - LocalInputLumaModification, tsr_half(0.0)));
|
|
}
|
|
|
|
#if 0
|
|
tsr_tensor_half InputBoxSize = InputLumaMax - InputLumaMin;
|
|
CurrentGradient = sign(CurrentGradient) * max(abs(CurrentGradient) - tsr_tensor_half::Const(0.5) * InputBoxSize, tsr_tensor_half::Const(0.0));
|
|
#endif
|
|
|
|
tsr_tensor_half IsFlicker;
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
tsr_half LocalCurrentGradient = CurrentGradient.GetElement(ElementIndex);
|
|
tsr_half LocalPrevGradient = PrevGradient.GetElement(ElementIndex);
|
|
|
|
bool bGradientsAreSameSign = LocalCurrentGradient * LocalPrevGradient > tsr_half(0.0);
|
|
bool bGradientsAreWithinError = abs(LocalCurrentGradient) < MoireEncodingError || abs(LocalPrevGradient) < MoireEncodingError;
|
|
//bool bCurrentGradientSubstentialEnough = abs(LocalCurrentGradient) > View.GeneralPurposeTweak * abs(LocalPrevGradient); // - 2.0 * MoireEncodingError;
|
|
|
|
tsr_half LocalIsFlicker = select((
|
|
bGradientsAreSameSign ||
|
|
bGradientsAreWithinError ||
|
|
//!bCurrentGradientSubstentialEnough ||
|
|
bCameraCut), tsr_half(0.0), tsr_half(1.0));
|
|
|
|
IsFlicker.SetElement(ElementIndex, LocalIsFlicker);
|
|
}
|
|
|
|
// Compute the total variation of the gradient over time.
|
|
tsr_tensor_half GradientVariation = min(abs(PrevGradient), abs(CurrentGradient)) * IsFlicker;
|
|
|
|
// Dilates the total variation and contribution to keep stability despite inacuracies in the history reprojection
|
|
// of these need a nearest filter which isn't precise at all.
|
|
tsr_tensor_half TotalVariationContribution;
|
|
tsr_tensor_half TotalVariationCountContribution;
|
|
Deconcatenate(Max3x3(Max3x3(Concatenate(GradientVariation, IsFlicker))), /* out */ TotalVariationContribution, /* out */ TotalVariationCountContribution);
|
|
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
tsr_half LocalCurrentGradient = CurrentGradient.GetElement(ElementIndex);
|
|
|
|
tsr_half LocalPrevGradient = PrevGradient.GetElement(ElementIndex);
|
|
tsr_half LocalPrevTotalVariation = PrevTotalVariation.GetElement(ElementIndex);
|
|
tsr_half LocalPrevTotalVariationCount = PrevTotalVariationCount.GetElement(ElementIndex);
|
|
bool LocalIsDisoccluded = bIsDisoccluded.GetElement(ElementIndex);
|
|
|
|
tsr_half LocalIsFlicker = IsFlicker.GetElement(ElementIndex);
|
|
|
|
tsr_half LocalTotalVariationContribution = TotalVariationContribution.GetElement(ElementIndex);
|
|
tsr_half LocalTotalVariationCountContribution = TotalVariationCountContribution.GetElement(ElementIndex);
|
|
|
|
// Blend out previous gradient
|
|
tsr_half LocalPrevBlendedGradient = LocalPrevGradient * (tsr_half(1.0) - MinBlendFinal) * (tsr_half(1.0) - LocalIsFlicker);
|
|
|
|
// Compute new gradient.
|
|
#if 1
|
|
tsr_half LocalNewGradient = LocalPrevBlendedGradient + LocalCurrentGradient;
|
|
#else
|
|
tsr_half LocalNewGradient = abs(LocalPrevBlendedGradient) > abs(LocalCurrentGradient) ? LocalPrevBlendedGradient : LocalCurrentGradient;
|
|
#endif
|
|
|
|
// Accumulate current frame variation.
|
|
tsr_half LocalNewTotalVariation = LocalPrevTotalVariation + LocalTotalVariationContribution;
|
|
tsr_half LocalNewTotalVariationCount = LocalPrevTotalVariationCount + LocalTotalVariationCountContribution;
|
|
|
|
// Discard all moire history on parallax disocclusion.
|
|
LocalNewGradient = select(LocalIsDisoccluded, tsr_half(0.0), LocalNewGradient);
|
|
LocalNewTotalVariation = select(LocalIsDisoccluded, tsr_half(0.0), LocalNewTotalVariation);
|
|
LocalNewTotalVariationCount = select(LocalIsDisoccluded, tsr_half(0.0), LocalNewTotalVariationCount);
|
|
|
|
// Quantise total variation and count to history bit depth to variation drift in the history over time.
|
|
tsr_half LocalQuantizedNewTotalVariationCount = floor(LocalNewTotalVariationCount * (tsr_half(255.0) / MaxPrevTotalVariationCount)) * (MaxPrevTotalVariationCount / tsr_half(255.0));
|
|
LocalNewTotalVariation *= LocalQuantizedNewTotalVariationCount * SafeRcp(LocalNewTotalVariationCount);
|
|
|
|
// Compute the final luminance
|
|
#if 1
|
|
tsr_half LocalCountFadeIn = saturate(LocalNewTotalVariationCount / TotalVariationCountThreshold - tsr_half(0.5));
|
|
#else
|
|
tsr_half LocalCountFadeIn = saturate(LocalNewTotalVariationCount - TotalVariationCountThreshold);
|
|
#endif
|
|
tsr_half LocalMoireError = (abs(LocalNewTotalVariation * SafeRcp(LocalNewTotalVariationCount)) + LocalNewTotalVariationCount * MoireEncodingError) * LocalCountFadeIn;
|
|
|
|
tsr_half4 PackedMoireHistory = tsr_half4(
|
|
FinalFlickeringHistory.GetElement(ElementIndex),
|
|
LocalNewGradient * tsr_half(127.0 / 255.0) + tsr_half(127.0 / 255.0),
|
|
LocalNewTotalVariation,
|
|
LocalQuantizedNewTotalVariationCount * rcp(MaxPrevTotalVariationCount));
|
|
|
|
OutMoireHistory.SetElement(ElementIndex, PackedMoireHistory);
|
|
|
|
OutMoireError.SetElement(ElementIndex, LocalMoireError);
|
|
}
|
|
|
|
// Moving object in the scene have very low chance of creating moire pattern with TAA jitter, so discard the moire error to still
|
|
// have character animation ghost free evem as environement shadow project different things on character.
|
|
OutMoireError = saturate(OutMoireError * bIsStatic - InputLumaModification);
|
|
} // ComputeMoireError()
|
|
|
|
|
|
//------------------------------------------------------- SHADING REJECTION
|
|
|
|
void MeasureRejection(
|
|
tsr_tensor_halfC InputC0,
|
|
tsr_tensor_halfC InputC2,
|
|
tsr_tensor_halfC HistoryC2,
|
|
tsr_tensor_half MoireError,
|
|
#if CONFIG_THIN_GEOMETRY_DETECTION
|
|
tsr_tensor_half HistoryRelaxationFactor,
|
|
const bool bEnableHistoryControl,
|
|
#endif
|
|
bool bEnableMoireError,
|
|
out tsr_tensor_half OutRejectionBlendFinal,
|
|
out tsr_tensor_half OutRejectionClampBlend)
|
|
{
|
|
tsr_tensor_halfC BackbufferQuantizationErrorVector = tsr_tensor_halfC::Const(MeasureBackbufferLDRQuantizationError());
|
|
|
|
tsr_tensor_halfC FilteredHistory = Blur3x3(HistoryC2);
|
|
|
|
tsr_tensor_halfC TotalVarInputDiffC0C2 = abs(TotalVariation3x3(abs(InputC0 - InputC2)));
|
|
tsr_tensor_halfC TotalVarInputC2 = abs(TotalVariation3x3(InputC2));
|
|
|
|
tsr_tensor_halfC ClampError = BackbufferQuantizationErrorVector;
|
|
ClampError = max(ClampError, Blur3x3(min(TotalVarInputDiffC0C2, TotalVarInputC2)));
|
|
|
|
tsr_tensor_halfC InputC2BoxSize = MaxMinusMin3x3(InputC2);
|
|
ClampError = max(ClampError, InputC2BoxSize * tsr_half(FilteringWeight * 0.25));
|
|
ClampError = ClampError + BackbufferQuantizationErrorVector;
|
|
|
|
tsr_tensor_halfC FilteredInput = Blur3x3(InputC2);
|
|
|
|
// Build the clamping box of the filtered signal
|
|
tsr_tensor_halfC FilteredBoxMin;
|
|
tsr_tensor_halfC FilteredBoxMax;
|
|
MinMax3x3(FilteredInput, /* out */ FilteredBoxMin, /* out */ FilteredBoxMax);
|
|
|
|
#if CONFIG_THIN_GEOMETRY_DETECTION
|
|
if (bEnableHistoryControl)
|
|
{
|
|
tsr_tensor_halfC LerpClampedHistoryInput = LerpClamp3x3(FilteredHistory, FilteredInput, HistoryRelaxationFactor);
|
|
#if CONFIG_COMPILE_FP16
|
|
tsr_tensor_halfC FilteredHistoryBoxMin;
|
|
tsr_tensor_halfC FilteredHistoryBoxMax;
|
|
MinMax3x3(LerpClampedHistoryInput, /* out */ FilteredHistoryBoxMin, /* out */ FilteredHistoryBoxMax);
|
|
#else
|
|
tsr_tensor_halfC FilteredHistoryBoxMin = Min3x3(LerpClampedHistoryInput);
|
|
tsr_tensor_halfC FilteredHistoryBoxMax = Max3x3(LerpClampedHistoryInput);
|
|
#endif
|
|
tsr_tensor_halfC ScalingFactor = tsr_tensor_halfC::Vectorize((HistoryRelaxationFactor));
|
|
FilteredBoxMin = select(ScalingFactor > tsr_tensor_halfC::Const(tsr_half(1.0f / 127.0f)), FilteredHistoryBoxMin, FilteredBoxMin);
|
|
FilteredBoxMax = select(ScalingFactor > tsr_tensor_halfC::Const(tsr_half(1.0f / 127.0f)), FilteredHistoryBoxMax, FilteredBoxMax);
|
|
}
|
|
#endif
|
|
|
|
FilteredBoxMin = FilteredBoxMin - ClampError;
|
|
FilteredBoxMax = FilteredBoxMax + ClampError;
|
|
|
|
// Clamp filtered signal
|
|
tsr_tensor_halfC ClampedFilteredHistory = fastClamp(FilteredHistory, FilteredBoxMin, FilteredBoxMax);
|
|
BRANCH
|
|
if (bEnableMoireError)
|
|
{
|
|
tsr_tensor_halfC MoireErrorSize;
|
|
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
#if 0
|
|
{
|
|
tsr_halfC ClampedEnergy = abs(FlickeringClampedFilteredHistory.GetElement(ElementIndex) - FilteredHistory.GetElement(ElementIndex));
|
|
tsr_halfC ClampedEnergyAmount = ClampedEnergy * SafeRcp(ClampedEnergy[0] + ClampedEnergy[1] + ClampedEnergy[2]);
|
|
|
|
// Apply the moire error on each individual channel based on how much they need to clamp.
|
|
MoireErrorSize.SetElement(ElementIndex, ClampedEnergyAmount * (kMoireLumaToChannel * MoireError.GetElement(ElementIndex)));
|
|
}
|
|
#else
|
|
{
|
|
MoireErrorSize.SetElement(ElementIndex, kMoireLumaToChannel * MoireError.GetElement(ElementIndex));
|
|
}
|
|
#endif
|
|
|
|
tsr_tensor_halfC StableFilteredBoxMin = min(FilteredBoxMin, FilteredBoxMax - MoireErrorSize);
|
|
tsr_tensor_halfC StableFilteredBoxMax = max(FilteredBoxMax, FilteredBoxMin + MoireErrorSize);
|
|
|
|
ClampedFilteredHistory = fastClamp(FilteredHistory, StableFilteredBoxMin, StableFilteredBoxMax);
|
|
}
|
|
|
|
// Compute the box size.
|
|
tsr_tensor_halfC BoxSize;
|
|
{
|
|
BoxSize = (
|
|
InputC2BoxSize * tsr_tensor_halfC::Const(FilteringWeight) +
|
|
(BackbufferQuantizationErrorVector * tsr_half(2 * FilteringWeight)));
|
|
|
|
BRANCH
|
|
if (bEnableMoireError)
|
|
{
|
|
tsr_tensor_halfC MoireErrorSize;
|
|
UNROLL_N(SIMD_SIZE)
|
|
for (uint ElementIndex = 0; ElementIndex < SIMD_SIZE; ElementIndex++)
|
|
{
|
|
tsr_halfC ClampedEnergy = abs(ClampedFilteredHistory.GetElement(ElementIndex) - FilteredHistory.GetElement(ElementIndex));
|
|
tsr_halfC ClampedEnergyAmount = ClampedEnergy * SafeRcp(ClampedEnergy[0] + ClampedEnergy[1] + ClampedEnergy[2]);
|
|
|
|
// Apply the moire error on each individual channel based on how much they need to clamp.
|
|
MoireErrorSize.SetElement(ElementIndex, ClampedEnergyAmount * ((FilteringWeight * tsr_half(3.0)) * MoireError.GetElement(ElementIndex)));
|
|
}
|
|
BoxSize = max(BoxSize, MoireErrorSize);
|
|
}
|
|
}
|
|
|
|
tsr_tensor_halfC Delta = max(abs(FilteredInput - FilteredHistory), BoxSize);
|
|
|
|
tsr_tensor_halfC RawClampedEnergy = abs(ClampedFilteredHistory - FilteredHistory);
|
|
|
|
tsr_tensor_halfC RawFactor = saturate(tsr_tensor_halfC::Const(1.0) - RawClampedEnergy * rcp(Delta));
|
|
tsr_tensor_half RawRejection = min3(RawFactor[0], RawFactor[1], RawFactor[2]);
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
RawRejection = min(RawRejection, RawFactor[3]);
|
|
#endif
|
|
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
OutRejectionClampBlend = Min3x3(Median3x3(RawRejection));
|
|
tsr_tensor_halfC FilteredClampedEnergy = Max3x3(Median3x3(RawClampedEnergy));
|
|
#else
|
|
tsr_tensor_halfC FilteredClampedEnergy;
|
|
Deconcatenate(MaxRGBMinA3x3(Median3x3(Concatenate(RawClampedEnergy, RawRejection))), /* out */ FilteredClampedEnergy, /* out */ OutRejectionClampBlend);
|
|
#endif
|
|
|
|
tsr_tensor_halfC FilteredFactor = saturate(tsr_tensor_halfC::Const(1.0) - FilteredClampedEnergy * rcp(Delta));
|
|
tsr_tensor_half FilteredRejection = min3(FilteredFactor[0], FilteredFactor[1], FilteredFactor[2]);
|
|
#if CONFIG_SCENE_COLOR_ALPHA
|
|
FilteredRejection = min(FilteredRejection, FilteredFactor[3]);
|
|
#endif
|
|
OutRejectionBlendFinal = FilteredRejection;
|
|
} // MeasureRejection()
|