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 /// /// Creates a new PostProcessPass instance. /// /// The PostProcessData resources to use. /// Requested GraphicsFormat for postprocess rendering. /// /// /// /// 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); } /// /// Disposes used resources. /// 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(); var cameraData = frameData.Get(); var postProcessingData = frameData.Get(); var stack = VolumeManager.instance.stack; var depthOfField = stack.GetComponent(); var motionBlur = stack.GetComponent(); var paniniProjection = stack.GetComponent(); var bloom = stack.GetComponent(); var lensFlareScreenSpace = stack.GetComponent(); 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(); var stack = VolumeManager.instance.stack; // NOTE: Debug handling injects a global state render pass. UpdateGlobalDebugHandlerPass(renderGraph, cameraData, true); var resourceData = frameData.Get(); 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 } }