Files
UnrealEngine/Engine/Shaders/Private/TemporalSuperResolution/TSRShadingAnalysis.ush
2025-05-18 13:04:45 +08:00

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()