using System;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using System.Runtime.CompilerServices; // AggressiveInlining
namespace UnityEngine.Rendering.Universal
{
internal sealed class PostProcess : IDisposable
{
// Passes
StopNanPostProcessPass m_StopNanPostProcessPass;
SmaaPostProcessPass m_SmaaPostProcessPass;
DepthOfFieldGaussianPostProcessPass m_DepthOfFieldGaussianPass;
DepthOfFieldBokehPostProcessPass m_DepthOfFieldBokehPass;
UpscalerPostProcessPass m_UpscalerPostProcessPass;
StpPostProcessPass m_StpPostProcessPass;
TemporalAntiAliasingPostProcessPass m_TemporalAntiAliasingPass;
MotionBlurPostProcessPass m_MotionBlurPass;
PaniniProjectionPostProcessPass m_PaniniProjectionPass;
BloomPostProcessPass m_BloomPass;
LensFlareScreenSpacePostProcessPass m_LensFlareScreenSpacePass;
LensFlareDataDrivenPostProcessPass m_LensFlareDataDrivenPass;
UberPostProcessPass m_UberPass;
// Final Passes
ScalingSetupPostProcessPass m_ScalingSetupFinalPostProcessPass;
Fsr1UpscalePostProcessPass m_Fsr1UpscaleFinalPostProcessPass;
FinalPostProcessPass m_FinalPostProcessPass;
PostProcessData m_Resources;
int m_DitheringTextureIndex; // 8-bit dithering
/// <summary>
/// Creates a new <c>PostProcessPass</c> instance.
/// </summary>
/// <param name="postProcessResourceAssetData">The <c>PostProcessData</c> resources to use.</param>
/// <param name="requestPostProColorFormat">Requested <c>GraphicsFormat</c> for postprocess rendering.</param>
/// <seealso cref="RenderPassEvent"/>
/// <seealso cref="PostProcessData"/>
/// <seealso cref="PostProcessParams"/>
/// <seealso cref="GraphicsFormat"/>
public PostProcess(PostProcessData postProcessResourceAssetData)
{
Assertions.Assert.IsNotNull(postProcessResourceAssetData, "PostProcessData and resources cannot be null.");
m_Resources = postProcessResourceAssetData;
m_StopNanPostProcessPass = new StopNanPostProcessPass(m_Resources.shaders.stopNanPS);
m_SmaaPostProcessPass = new SmaaPostProcessPass(m_Resources.shaders.subpixelMorphologicalAntialiasingPS, m_Resources.textures.smaaAreaTex, m_Resources.textures.smaaSearchTex);
m_DepthOfFieldGaussianPass = new DepthOfFieldGaussianPostProcessPass(m_Resources.shaders.gaussianDepthOfFieldPS);
m_DepthOfFieldBokehPass = new DepthOfFieldBokehPostProcessPass(m_Resources.shaders.bokehDepthOfFieldPS);
m_UpscalerPostProcessPass = new UpscalerPostProcessPass(m_Resources.textures.blueNoise16LTex);
m_StpPostProcessPass = new StpPostProcessPass(m_Resources.textures.blueNoise16LTex);
m_TemporalAntiAliasingPass = new TemporalAntiAliasingPostProcessPass(m_Resources.shaders.temporalAntialiasingPS);
m_MotionBlurPass = new MotionBlurPostProcessPass(m_Resources.shaders.cameraMotionBlurPS);
m_PaniniProjectionPass = new PaniniProjectionPostProcessPass(m_Resources.shaders.paniniProjectionPS);
m_BloomPass = new BloomPostProcessPass(m_Resources.shaders.bloomPS);
m_LensFlareScreenSpacePass = new LensFlareScreenSpacePostProcessPass(m_Resources.shaders.LensFlareScreenSpacePS);
m_LensFlareDataDrivenPass = new LensFlareDataDrivenPostProcessPass(m_Resources.shaders.LensFlareDataDrivenPS);
m_UberPass = new UberPostProcessPass(m_Resources.shaders.uberPostPS, m_Resources.textures.filmGrainTex);
// Final post processing.
m_ScalingSetupFinalPostProcessPass = new ScalingSetupPostProcessPass(m_Resources.shaders.scalingSetupPS);
m_Fsr1UpscaleFinalPostProcessPass = new Fsr1UpscalePostProcessPass(m_Resources.shaders.easuPS);
m_FinalPostProcessPass = new FinalPostProcessPass(m_Resources.shaders.finalPostPassPS, m_Resources.textures.filmGrainTex);
}
/// <summary>
/// Disposes used resources.
/// </summary>
public void Dispose()
{
m_FinalPostProcessPass?.Dispose();
m_Fsr1UpscaleFinalPostProcessPass?.Dispose();
m_ScalingSetupFinalPostProcessPass?.Dispose();
m_UberPass?.Dispose();
m_LensFlareDataDrivenPass?.Dispose();
m_LensFlareScreenSpacePass?.Dispose();
m_BloomPass?.Dispose();
m_PaniniProjectionPass?.Dispose();
m_MotionBlurPass?.Dispose();
m_TemporalAntiAliasingPass?.Dispose();
m_StpPostProcessPass?.Dispose();
m_UpscalerPostProcessPass.Dispose();
m_DepthOfFieldBokehPass?.Dispose();
m_DepthOfFieldGaussianPass?.Dispose();
m_SmaaPostProcessPass?.Dispose();
m_StopNanPostProcessPass?.Dispose();
}
// Some Android devices do not support sRGB backbuffer
// We need to do the conversion manually on those
// Also if HDR output is active
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool RequireSRGBConversionBlitToBackBuffer(UniversalCameraData cameraData, bool enableColorEncodingIfNeeded)
{
return cameraData.requireSrgbConversion && enableColorEncodingIfNeeded;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
int GetNextDitherIndex()
{
#if LWRP_DEBUG_STATIC_POSTFX // Used by QA for automated testing
return 0;
#else
if (++m_DitheringTextureIndex >= m_Resources.textures.blueNoise16LTex.Length)
m_DitheringTextureIndex = 0;
return m_DitheringTextureIndex;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Texture2D GetNextDitherTexture()
{
return m_Resources.textures.blueNoise16LTex[GetNextDitherIndex()];
}
static void UpdateGlobalDebugHandlerPass(RenderGraph renderGraph, UniversalCameraData cameraData, bool isFinalPass)
{
// NOTE: Debug handling injects a global state render pass.
DebugHandler debugHandler = ScriptableRenderPass.GetActiveDebugHandler(cameraData);
bool resolveToDebugScreen = debugHandler != null && debugHandler.WriteToDebugScreenTexture(cameraData.resolveFinalTarget);
debugHandler?.UpdateShaderGlobalPropertiesForFinalValidationPass(renderGraph, cameraData, isFinalPass && !resolveToDebugScreen);
}
// If hasFinalPass == true, Film Grain and Dithering are setup in the final pass, otherwise they are setup in this pass.
public void RenderPostProcessing(RenderGraph renderGraph, ContextContainer frameData, bool hasFinalPass, bool enableColorEncodingIfNeeded)
{
var resourceData = frameData.Get<UniversalResourceData>();
var cameraData = frameData.Get<UniversalCameraData>();
var postProcessingData = frameData.Get<UniversalPostProcessingData>();
var stack = VolumeManager.instance.stack;
var depthOfField = stack.GetComponent<DepthOfField>();
var motionBlur = stack.GetComponent<MotionBlur>();
var paniniProjection = stack.GetComponent<PaniniProjection>();
var bloom = stack.GetComponent<Bloom>();
var lensFlareScreenSpace = stack.GetComponent<ScreenSpaceLensFlare>();
bool useFastSRGBLinearConversion = postProcessingData.useFastSRGBLinearConversion;
bool supportDataDrivenLensFlare = postProcessingData.supportDataDrivenLensFlare;
bool supportScreenSpaceLensFlare = postProcessingData.supportScreenSpaceLensFlare;
bool isSceneViewCamera = cameraData.isSceneViewCamera;
//We blit back and forth without msaa untill the last blit.
bool useStopNan = cameraData.isStopNaNEnabled;
bool useSubPixelMorpAA = (cameraData.antialiasing == AntialiasingMode.SubpixelMorphologicalAntiAliasing);
bool useDepthOfField = depthOfField.IsActive() && !isSceneViewCamera;
bool useLensFlare = !LensFlareCommonSRP.Instance.IsEmpty() && supportDataDrivenLensFlare;
bool useLensFlareScreenSpace = lensFlareScreenSpace.IsActive() && supportScreenSpaceLensFlare;
bool useMotionBlur = motionBlur.IsActive() && !isSceneViewCamera;
bool usePaniniProjection = paniniProjection.IsActive() && !isSceneViewCamera;
// Disable MotionBlur in EditMode, so that editing remains clear and readable.
// NOTE: HDRP does the same via CoreUtils::AreAnimatedMaterialsEnabled().
// Disable MotionBlurMode.CameraAndObjects on renderers that do not support motion vectors
useMotionBlur = useMotionBlur && Application.isPlaying;
if (useMotionBlur && motionBlur.mode.value == MotionBlurMode.CameraAndObjects)
{
ScriptableRenderer renderer = cameraData.renderer;
useMotionBlur &= renderer.SupportsMotionVectors();
if (!useMotionBlur)
{
var warning = "Disabling Motion Blur for Camera And Objects because the renderer does not implement motion vectors.";
const int warningThrottleFrames = 60 * 1; // 60 FPS * 1 sec
if (Time.frameCount % warningThrottleFrames == 0)
Debug.LogWarning(warning);
}
}
// Note that enabling jitters uses the same CameraData::IsTemporalAAEnabled(). So if we add any other kind of overrides (like
// disable useTemporalAA if another feature is disabled) then we need to put it in CameraData::IsTemporalAAEnabled() as opposed
// to tweaking the value here.
bool useTemporalAA = cameraData.IsTemporalAAEnabled() && m_TemporalAntiAliasingPass.IsValid();
// STP is only enabled when TAA is enabled and all of its runtime requirements are met.
// Using IsSTPRequested() vs IsSTPEnabled() for perf reason here, as we already know TAA status
bool isSTPRequested = cameraData.IsSTPRequested();
bool useSTP = useTemporalAA && isSTPRequested;
// Warn users if TAA and STP are disabled despite being requested
if (!useTemporalAA && cameraData.IsTemporalAARequested())
TemporalAA.ValidateAndWarn(cameraData, isSTPRequested);
// NOTE: Debug handling injects a global state render pass.
UpdateGlobalDebugHandlerPass(renderGraph, cameraData, !hasFinalPass);
var colorSourceDesc = resourceData.cameraColor.GetDescriptor(renderGraph);
// Optional NaN killer before post-processing kicks in
// stopNaN may be null on Adreno 3xx. It doesn't support full shader level 3.5, but SystemInfo.graphicsShaderLevel is 35.
if (useStopNan)
{
m_StopNanPostProcessPass.RecordRenderGraph(renderGraph, frameData);
}
if(useSubPixelMorpAA)
{
m_SmaaPostProcessPass.Setup(cameraData.antialiasingQuality);
m_SmaaPostProcessPass.RecordRenderGraph(renderGraph, frameData);
}
// Depth of Field
// Adreno 3xx SystemInfo.graphicsShaderLevel is 35, but instancing support is disabled due to buggy drivers.
// DOF shader uses #pragma target 3.5 which adds requirement for instancing support, thus marking the shader unsupported on those devices.
if (useDepthOfField)
{
if(depthOfField.mode.value == DepthOfFieldMode.Gaussian)
{
m_DepthOfFieldGaussianPass.RecordRenderGraph(renderGraph, frameData);
}
else
{
m_DepthOfFieldBokehPass.Setup(useFastSRGBLinearConversion);
m_DepthOfFieldBokehPass.RecordRenderGraph(renderGraph, frameData);
}
}
// Temporal Anti Aliasing / Upscaling
if (useTemporalAA)
{
#if ENABLE_UPSCALER_FRAMEWORK
if (postProcessingData.activeUpscaler != null)
{
m_UpscalerPostProcessPass.RecordRenderGraph(renderGraph, frameData);
}
else
#endif
if (useSTP)
{
m_StpPostProcessPass.RecordRenderGraph(renderGraph, frameData);
}
else
{
m_TemporalAntiAliasingPass.RecordRenderGraph(renderGraph, frameData);
}
}
if(useMotionBlur)
{
m_MotionBlurPass.RecordRenderGraph(renderGraph, frameData);
}
if(usePaniniProjection)
{
m_PaniniProjectionPass.RecordRenderGraph(renderGraph, frameData);
}
// Uberpost
{
// Bloom goes first
bool bloomActive = bloom.IsActive() || useLensFlareScreenSpace;
//Even if bloom is not active we need the texture if the lensFlareScreenSpace pass is active.
if (bloomActive)
{
// NOTE: bloom destination texture is some texture in the bloom mip pyramid. It's not explicitly set beforehand.
m_BloomPass.RecordRenderGraph(renderGraph, frameData);
if (useLensFlareScreenSpace)
{
var mipPyramid = m_BloomPass.mipPyramid;
// TODO: Bloom pass could compute the flare source resource.
// We need to take into account how many valid mips the bloom pass produced.
int bloomMipCount = mipPyramid.mipCount;
int bloomMipMax = Mathf.Clamp(bloomMipCount - 1, 0, bloom.maxIterations.value / 2);
int bloomMipIndex = Mathf.Clamp(lensFlareScreenSpace.bloomMip.value, 0, bloomMipMax);
var prevCameraColor = resourceData.cameraColor;
resourceData.cameraColor = mipPyramid.GetResultMip(bloomMipIndex);;
m_LensFlareScreenSpacePass.Setup(colorSourceDesc.width, colorSourceDesc.height, bloomMipIndex);
m_LensFlareScreenSpacePass.RecordRenderGraph(renderGraph, frameData);
resourceData.cameraColor = prevCameraColor;
}
}
if (useLensFlare)
{
// Lens Flares are procedurally generated and blended to the destination texture.
m_LensFlareDataDrivenPass.RecordRenderGraph(renderGraph, frameData);
}
var ditherTexture = cameraData.isDitheringEnabled ? GetNextDitherTexture() : null;
var hdrOperations = HDROutputUtils.Operation.None;
var applySrgbEncoding = RequireSRGBConversionBlitToBackBuffer(cameraData, enableColorEncodingIfNeeded);
bool requireHDROutput = PostProcessUtils.RequireHDROutput(cameraData);
if (requireHDROutput)
{
// Color space conversion is already applied through color grading, do encoding if uber post is the last pass
// Otherwise encoding will happen in the final post process pass or the final blit pass
hdrOperations = !hasFinalPass && enableColorEncodingIfNeeded ? HDROutputUtils.Operation.ColorEncoding : HDROutputUtils.Operation.None;
}
UberPostProcessPass.FilteringOperation filteringOperation = UberPostProcessPass.FilteringOperation.Linear;
// Point sampling is only used for upscaling so the default linear sampler should be used if there is a final pass
if (cameraData.imageScalingMode == ImageScalingMode.Upscaling && !hasFinalPass && cameraData.upscalingFilter == ImageUpscalingFilter.Point)
{
filteringOperation = UberPostProcessPass.FilteringOperation.Point;
}
bool renderOverlayUI = requireHDROutput && enableColorEncodingIfNeeded && resourceData.overlayUITexture.IsValid();
m_UberPass.Setup(ditherTexture, filteringOperation, hdrOperations, applySrgbEncoding, useFastSRGBLinearConversion, !hasFinalPass, renderOverlayUI);
m_UberPass.RecordRenderGraph(renderGraph, frameData);
}
}
#region FinalPass
public void RenderFinalPostProcessing(RenderGraph renderGraph, ContextContainer frameData, bool enableColorEncodingIfNeeded)
{
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
var stack = VolumeManager.instance.stack;
// NOTE: Debug handling injects a global state render pass.
UpdateGlobalDebugHandlerPass(renderGraph, cameraData, true);
var resourceData = frameData.Get<UniversalResourceData>();
var sourceDesc = renderGraph.GetTextureDesc(resourceData.cameraColor);
HDROutputUtils.Operation hdrOperations = HDROutputUtils.Operation.None;
bool requireHDROutput = PostProcessUtils.RequireHDROutput(cameraData);
if (requireHDROutput)
{
// Final post process pass always does color encoding
hdrOperations = enableColorEncodingIfNeeded ? HDROutputUtils.Operation.ColorEncoding : HDROutputUtils.Operation.None;
// If the color space conversion wasn't applied by the uber pass, do it here
if (!cameraData.postProcessEnabled)
hdrOperations |= HDROutputUtils.Operation.ColorConversion;
}
FinalPostProcessPass.FilteringOperation filteringOperation = FinalPostProcessPass.FilteringOperation.Linear;
// Reuse RCAS pass as an optional standalone post sharpening pass for TAA.
// This avoids the cost of EASU and is available for other upscaling options.
// If FSR is enabled then FSR settings override the TAA settings and we perform RCAS only once.
// If STP is enabled, then TAA sharpening has already been performed inside STP.
if(PostProcessUtils.IsTaaSharpeningEnabled(cameraData))
filteringOperation = FinalPostProcessPass.FilteringOperation.TaaSharpening;
bool applyFxaa = PostProcessUtils.IsFxaaEnabled(cameraData);
if (cameraData.imageScalingMode != ImageScalingMode.None)
{
// When FXAA is enabled in scaled renders, we execute it in a separate blit since it's not designed to be used in
// situations where the input and output resolutions do not match.
// When FSR is active, we always need an additional pass since it has a very particular color encoding requirement.
// NOTE: An ideal implementation could inline this color conversion logic into the UberPost pass, but the current code structure would make
// this process very complex. Specifically, we'd need to guarantee that the uber post output is always written to a UNORM format render
// target in order to preserve the precision of specially encoded color data.
bool isSetupRequired = (applyFxaa || PostProcessUtils.IsFsrEnabled(cameraData));
// When FXAA is needed while scaling is active, we must perform it before the scaling takes place.
if (isSetupRequired)
{
m_ScalingSetupFinalPostProcessPass.Setup(hdrOperations);
m_ScalingSetupFinalPostProcessPass.RecordRenderGraph(renderGraph, frameData);
// Indicate that we no longer need to perform FXAA in the final pass since it was already perfomed here.
applyFxaa = false;
}
// Upscaling (and downscaling)
var upscaledDesc = sourceDesc;
upscaledDesc.width = cameraData.pixelWidth;
upscaledDesc.height = cameraData.pixelHeight;
// NOTE: upscaledDesc.format != scalingSetupDesc.format (in resourceData.cameraColor)
switch (cameraData.imageScalingMode)
{
case ImageScalingMode.Upscaling:
{
switch (cameraData.upscalingFilter)
{
case ImageUpscalingFilter.Point:
{
// TAA post sharpening is an RCAS pass, avoid overriding it with point sampling.
if (filteringOperation != FinalPostProcessPass.FilteringOperation.TaaSharpening)
filteringOperation = FinalPostProcessPass.FilteringOperation.Point;
break;
}
case ImageUpscalingFilter.Linear:
{
break;
}
case ImageUpscalingFilter.FSR:
{
m_Fsr1UpscaleFinalPostProcessPass.Setup(upscaledDesc);
m_Fsr1UpscaleFinalPostProcessPass.RecordRenderGraph(renderGraph, frameData);
filteringOperation = FinalPostProcessPass.FilteringOperation.FsrSharpening;
break;
}
}
break;
}
case ImageScalingMode.Downscaling:
{
// In the downscaling case, we don't perform any sort of filter override logic since we always want linear filtering
// and it's already the default option in the shader.
// Also disable TAA post sharpening pass when downscaling.
filteringOperation = FinalPostProcessPass.FilteringOperation.Linear;
break;
}
}
}
var ditherTexture = cameraData.isDitheringEnabled ? GetNextDitherTexture() : null;
bool applySrgbEncoding = RequireSRGBConversionBlitToBackBuffer(cameraData, enableColorEncodingIfNeeded);
bool renderOverlayUI = requireHDROutput && enableColorEncodingIfNeeded && cameraData.rendersOverlayUI;
m_FinalPostProcessPass.Setup(ditherTexture, filteringOperation, hdrOperations, applySrgbEncoding, applyFxaa, renderOverlayUI);
m_FinalPostProcessPass.RecordRenderGraph(renderGraph, frameData);
}
#endregion
}
}