/// <summary>
        ///   Initializes a new instance of the <see cref="PostProcessingEffects"/> class.
        /// </summary>
        public PostProcessingEffects()
        {
            Outline = new Outline {
                Enabled = false
            };
            Fog = new Fog {
                Enabled = false
            };

            AmbientOcclusion     = new AmbientOcclusion();
            LocalReflections     = new LocalReflections();
            DepthOfField         = new DepthOfField();
            luminanceEffect      = new LuminanceEffect();
            BrightFilter         = new BrightFilter();
            Bloom                = new Bloom();
            LightStreak          = new LightStreak();
            LensFlare            = new LensFlare();
            Antialiasing         = new FXAAEffect();
            rangeCompress        = new ImageEffectShader("RangeCompressorShader");
            rangeDecompress      = new ImageEffectShader("RangeDecompressorShader");
            colorTransformsGroup = new ColorTransformGroup();
        }
        protected override void DrawCore(RenderDrawContext context)
        {
            var input  = GetInput(0);
            var output = GetOutput(0);

            if (input is null || output is null)
            {
                return;
            }

            var inputDepthTexture = GetInput(1); // Depth

            // Update the parameters for this post effect
            if (!Enabled)
            {
                if (input != output)
                {
                    Scaler.SetInput(input);
                    Scaler.SetOutput(output);
                    Scaler.Draw(context);
                }
                return;
            }

            // If input == output, copy the input to a temporary texture
            if (input == output)
            {
                var newInput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                context.CommandList.Copy(input, newInput);
                input = newInput;
            }

            var currentInput = input;

            // Draw outline before AA
            if (Outline.Enabled && inputDepthTexture != null)
            {
                // Outline
                var outlineOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                Outline.SetColorDepthInput(currentInput, inputDepthTexture, context.RenderContext.RenderView.NearClipPlane, context.RenderContext.RenderView.FarClipPlane);
                Outline.SetOutput(outlineOutput);
                Outline.Draw(context);
                currentInput = outlineOutput;
            }

            var  fxaa    = Antialiasing as FXAAEffect;
            bool aaFirst = Bloom != null && Bloom.StableConvolution;
            bool needAA  = Antialiasing != null && Antialiasing.Enabled;

            // Do AA here, first. (hybrid method from Karis2013)
            if (aaFirst && needAA)
            {
                // do AA:
                if (fxaa != null)
                {
                    fxaa.InputLuminanceInAlpha = true;
                }

                var bufferIndex = 1;
                if (Antialiasing.RequiresDepthBuffer)
                {
                    Antialiasing.SetInput(bufferIndex++, inputDepthTexture);
                }

                bool requiresVelocityBuffer = Antialiasing.RequiresVelocityBuffer;
                if (requiresVelocityBuffer)
                {
                    Antialiasing.SetInput(bufferIndex++, GetInput(6));
                }

                var aaSurface = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                if (Antialiasing.NeedRangeDecompress)
                {
                    // Explanation:
                    //   The Karis method (Unreal Engine 4.1x), uses a hybrid pipeline to execute AA.
                    //   The AA is usually done at the end of the pipeline, but we don't benefit from
                    //   AA for the posteffects, which is a shame.
                    //   The Karis method, executes AA at the beginning, but for AA to be correct, it
                    //   must work post tonemapping, and even more in fact, in gamma space too. Plus,
                    //   it waits for the alpha=luma to be a "perceptive luma" so also gamma space.
                    //   In our case, working in gamma space created monstruous outlining artefacts
                    //   around exageratedly strong constrasted objects (way in HDR range).
                    //   So AA works in linear space, but still with gamma luma, as a light tradeoff
                    //   to supress artefacts.

                    // Create a 16 bits target for FXAA:

                    // Render range compression & perceptual luma to alpha channel:
                    rangeCompress.SetInput(currentInput);
                    rangeCompress.SetOutput(aaSurface);
                    rangeCompress.Draw(context);

                    Antialiasing.SetInput(0, aaSurface);
                    Antialiasing.SetOutput(currentInput);
                    Antialiasing.Draw(context);

                    // Reverse tone LDR to HDR:
                    rangeDecompress.SetInput(currentInput);
                    rangeDecompress.SetOutput(aaSurface);
                    rangeDecompress.Draw(context);
                }
                else
                {
                    Antialiasing.SetInput(0, currentInput);
                    Antialiasing.SetOutput(aaSurface);
                    Antialiasing.Draw(context);
                }

                currentInput = aaSurface;
            }

            if (AmbientOcclusion.Enabled && inputDepthTexture != null)
            {
                // Ambient Occlusion
                var aoOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                AmbientOcclusion.SetColorDepthInput(currentInput, inputDepthTexture);
                AmbientOcclusion.SetOutput(aoOutput);
                AmbientOcclusion.Draw(context);
                currentInput = aoOutput;
            }

            if (LocalReflections.Enabled && inputDepthTexture != null)
            {
                var normalsBuffer           = GetInput(2);
                var specularRoughnessBuffer = GetInput(3);

                if (normalsBuffer != null && specularRoughnessBuffer != null)
                {
                    // Local reflections
                    var rlrOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                    LocalReflections.SetInputSurfaces(currentInput, inputDepthTexture, normalsBuffer, specularRoughnessBuffer);
                    LocalReflections.SetOutput(rlrOutput);
                    LocalReflections.Draw(context);
                    currentInput = rlrOutput;
                }
            }

            if (DepthOfField.Enabled && inputDepthTexture != null)
            {
                // DoF
                var dofOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                DepthOfField.SetColorDepthInput(currentInput, inputDepthTexture);
                DepthOfField.SetOutput(dofOutput);
                DepthOfField.Draw(context);
                currentInput = dofOutput;
            }

            if (Fog.Enabled && inputDepthTexture != null)
            {
                // Fog
                var fogOutput = NewScopedRenderTarget2D(input.Width, input.Height, input.Format);
                Fog.SetColorDepthInput(currentInput, inputDepthTexture, context.RenderContext.RenderView.NearClipPlane, context.RenderContext.RenderView.FarClipPlane);
                Fog.SetOutput(fogOutput);
                Fog.Draw(context);
                currentInput = fogOutput;
            }

            // Luminance pass (only if tone mapping is enabled)
            // TODO: This is not super pluggable to have this kind of dependencies. Check how to improve this
            var toneMap = colorTransformsGroup.Transforms.Get <ToneMap>();

            if (colorTransformsGroup.Enabled && toneMap != null && toneMap.Enabled)
            {
                Texture luminanceTexture = null;
                if (toneMap.UseLocalLuminance)
                {
                    const int localLuminanceDownScale = 3;

                    // The luminance chain uses power-of-two intermediate targets, so it expects to output to one as well
                    var lumWidth = Math.Min(MathUtil.NextPowerOfTwo(currentInput.Size.Width), MathUtil.NextPowerOfTwo(currentInput.Size.Height));
                    lumWidth = Math.Max(1, lumWidth / 2);

                    var lumSize = new Size3(lumWidth, lumWidth, 1).Down2(localLuminanceDownScale);
                    luminanceTexture = NewScopedRenderTarget2D(lumSize.Width, lumSize.Height, PixelFormat.R16_Float, 1);

                    luminanceEffect.SetOutput(luminanceTexture);
                }

                luminanceEffect.EnableLocalLuminanceCalculation = toneMap.UseLocalLuminance;
                luminanceEffect.SetInput(currentInput);
                luminanceEffect.Draw(context);

                // Set this parameter that will be used by the tone mapping
                colorTransformsGroup.Parameters.Set(LuminanceEffect.LuminanceResult, new LuminanceResult(luminanceEffect.AverageLuminance, luminanceTexture));
            }

            if (BrightFilter.Enabled && (Bloom.Enabled || LightStreak.Enabled || LensFlare.Enabled))
            {
                Texture brightTexture = NewScopedRenderTarget2D(currentInput.Width, currentInput.Height, currentInput.Format, 1);

                // Bright filter pass

                BrightFilter.SetInput(currentInput);
                BrightFilter.SetOutput(brightTexture);
                BrightFilter.Draw(context);

                // Bloom pass
                if (Bloom.Enabled)
                {
                    Bloom.SetInput(brightTexture);
                    Bloom.SetOutput(currentInput);
                    Bloom.Draw(context);
                }

                // Light streak pass
                if (LightStreak.Enabled)
                {
                    LightStreak.SetInput(brightTexture);
                    LightStreak.SetOutput(currentInput);
                    LightStreak.Draw(context);
                }

                // Lens flare pass
                if (LensFlare.Enabled)
                {
                    LensFlare.SetInput(brightTexture);
                    LensFlare.SetOutput(currentInput);
                    LensFlare.Draw(context);
                }
            }

            bool aaLast     = needAA && !aaFirst;
            var  toneOutput = aaLast ? NewScopedRenderTarget2D(input.Width, input.Height, input.Format) : output;

            // When FXAA is enabled we need to detect whether the ColorTransformGroup should output the Luminance into the alpha or not
            var luminanceToChannelTransform = colorTransformsGroup.PostTransforms.Get <LuminanceToChannelTransform>();

            if (fxaa != null)
            {
                if (luminanceToChannelTransform is null)
                {
                    luminanceToChannelTransform = new LuminanceToChannelTransform {
                        ColorChannel = ColorChannel.A
                    };
                    colorTransformsGroup.PostTransforms.Add(luminanceToChannelTransform);
                }

                // Only enabled when FXAA is enabled and InputLuminanceInAlpha is true
                luminanceToChannelTransform.Enabled = fxaa.Enabled && fxaa.InputLuminanceInAlpha;
            }
            else if (luminanceToChannelTransform != null)
            {
                luminanceToChannelTransform.Enabled = false;
            }

            // Color transform group pass (tonemap, color grading)
            var lastEffect = colorTransformsGroup.Enabled ? (ImageEffect)colorTransformsGroup : Scaler;

            lastEffect.SetInput(currentInput);
            lastEffect.SetOutput(toneOutput);
            lastEffect.Draw(context);

            // Do AA here, last, if not already done.
            if (aaLast)
            {
                Antialiasing.SetInput(toneOutput);
                Antialiasing.SetOutput(output);
                Antialiasing.Draw(context);
            }
        }