using System;
using UnityEngine.Rendering.RenderGraphModule;
using System.Runtime.CompilerServices; // AggressiveInlining

namespace UnityEngine.Rendering.Universal
{
    internal sealed class ScalingSetupPostProcessPass : PostProcessPass
    {
        public const string k_TargetName = "_ScalingSetupTarget";

        Material m_Material;
        bool m_IsValid;

        HDROutputUtils.Operation m_HdrOperations;

        public ScalingSetupPostProcessPass(Shader shader)
        {
            this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
            this.profilingSampler = new ProfilingSampler("Setup Final Post Processing");

            m_Material = PostProcessUtils.LoadShader(shader, passName);
            m_IsValid = m_Material != null;
        }

        public override void Dispose()
        {
            CoreUtils.Destroy(m_Material);
            m_IsValid = false;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Setup(HDROutputUtils.Operation hdrOperations)
        {
            m_HdrOperations = hdrOperations;
        }

        private class PostProcessingFinalSetupPassData
        {
            internal TextureHandle destinationTexture;
            internal TextureHandle sourceTexture;
            internal Material material;
            internal UniversalCameraData cameraData;
            internal Tonemapping tonemapping;
            internal HDROutputUtils.Operation hdrOperations;
        }

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            if (!m_IsValid)
                return;

            var tonemapping = volumeStack.GetComponent<Tonemapping>();

            var cameraData = frameData.Get<UniversalCameraData>();
            var resourceData = frameData.Get<UniversalResourceData>();

            var sourceTexture = resourceData.cameraColor;

            var scalingSetupDesc = renderGraph.GetTextureDesc(sourceTexture);
            bool requireHDROutput = PostProcessUtils.RequireHDROutput(cameraData);
            if (!requireHDROutput)
            {
                // Select a UNORM format since we've already performed tonemapping. (Values are in 0-1 range)
                // This improves precision and is required if we want to avoid excessive banding when FSR is in use.
                scalingSetupDesc.format = UniversalRenderPipeline.MakeUnormRenderTextureGraphicsFormat();
            }

            var destinationTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, scalingSetupDesc, k_TargetName, true, FilterMode.Point);

            // Scaled FXAA
            using (var builder = renderGraph.AddRasterRenderPass<PostProcessingFinalSetupPassData>(passName, out var passData, profilingSampler))
            {
                passData.destinationTexture = destinationTexture;
                builder.SetRenderAttachment(destinationTexture, 0, AccessFlags.Write);
                passData.sourceTexture = sourceTexture;
                builder.UseTexture(sourceTexture, AccessFlags.Read);

                passData.material = m_Material;
                passData.cameraData = cameraData;
                passData.tonemapping = tonemapping;
                passData.hdrOperations = m_HdrOperations;

                builder.SetRenderFunc(static (PostProcessingFinalSetupPassData data, RasterGraphContext context) =>
                {
                    UniversalCameraData cameraData = data.cameraData;
                    Material material = data.material;
                    material.shaderKeywords = null;

                    bool hdrColorEncoding = data.hdrOperations.HasFlag(HDROutputUtils.Operation.ColorEncoding);
                    bool isFxaaEnabled = PostProcessUtils.IsFxaaEnabled(cameraData);
                    bool isFsrEnabled = PostProcessUtils.IsFsrEnabled(cameraData);

                    if (isFxaaEnabled)
                        material.EnableKeyword(ShaderKeywordStrings.Fxaa);

                    if (isFsrEnabled)
                        material.EnableKeyword( hdrColorEncoding ? ShaderKeywordStrings.Gamma20AndHDRInput : ShaderKeywordStrings.Gamma20);

                    if (hdrColorEncoding)
                        PostProcessUtils.SetupHDROutput(material, cameraData.hdrDisplayInformation, cameraData.hdrDisplayColorGamut, data.tonemapping, data.hdrOperations, cameraData.rendersOverlayUI);

                    if (data.cameraData.isAlphaOutputEnabled)
                        CoreUtils.SetKeyword(material, ShaderKeywordStrings._ENABLE_ALPHA_OUTPUT, cameraData.isAlphaOutputEnabled);

                    material.SetVector(ShaderConstants._SourceSize, PostProcessUtils.CalcShaderSourceSize(data.sourceTexture));

                    const bool isFinalPass = false; // This is a pass just before final pass. Viewport must match intermediate target.
                    PostProcessUtils.ScaleViewportAndBlit(context, data.sourceTexture, data.destinationTexture, data.cameraData, data.material, isFinalPass);
                });
            }

            resourceData.cameraColor = destinationTexture;
        }


        // Precomputed shader ids to same some CPU cycles (mostly affects mobile)
        public static class ShaderConstants
        {
            public static readonly int _SourceSize = Shader.PropertyToID("_SourceSize");
        }
    }
}