﻿//////////////////////////////////////////////////////
// MK Glow URP Renderer Feature	    		        //
//					                                //
// Created by Michael Kremmel                       //
// www.michaelkremmel.de                            //
// Copyright © 2020 All rights reserved.            //
//////////////////////////////////////////////////////

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
#if UNITY_2023_3_OR_NEWER
using UnityEngine.Rendering.RenderGraphModule;
#endif

#pragma warning disable CS0414

namespace MK.Glow.URP
{
    public class MKGlowLiteRendererFeature : ScriptableRendererFeature, MK.Glow.ISettings
    {
        /////////////////////////////////////////////////////////////////////////////////////////////
        // Renderer Feature Only Properties
        /////////////////////////////////////////////////////////////////////////////////////////////
        #if UNITY_EDITOR
        public bool showEditorMainBehavior = true;
		public bool showEditorBloomBehavior;
		public bool showEditorLensSurfaceBehavior;
        #endif

        public enum Workmode
        {
            [UnityEngine.InspectorName("Post-Process Volumes")]
            PostProcessVolumes = 0,
            [UnityEngine.InspectorName("Global")]
            Global = 1
        };
        public Workmode workmode = Workmode.PostProcessVolumes;

        ///Main
        public DebugView debugView = MK.Glow.DebugView.None;
        public Workflow workflow = MK.Glow.Workflow.Threshold;
        public LayerMask selectiveRenderLayerMask = -1;
        [Range(-1f, 1f)]
        public float anamorphicRatio = 0f;

        //Bloom
        [MK.Glow.MinMaxRange(0, 10)]
        public MinMaxRange bloomThreshold = new MinMaxRange(1.25f, 10f);
        [Range(1f, 10f)]
		public float bloomScattering = 7f;
		public float bloomIntensity = 1f;

        //LensSurface
        public bool allowLensSurface = false;
		public Texture2D lensSurfaceDirtTexture;
		public float lensSurfaceDirtIntensity = 2.5f;
		public Texture2D lensSurfaceDiffractionTexture;
		public float lensSurfaceDiffractionIntensity = 2.0f;

        /////////////////////////////////////////////////////////////////////////////////////////////
        // Settings
        /////////////////////////////////////////////////////////////////////////////////////////////
        public bool GetAllowGeometryShaders()
        { 
            return false;
        }
        public bool GetAllowComputeShaders()
        { 
            return false;
        }
        public MK.Glow.DebugView GetDebugView()
        { 
			return debugView;
		}
        public MK.Glow.Workflow GetWorkflow()
        { 
			return workflow;
		}
        public LayerMask GetSelectiveRenderLayerMask()
        { 
			return selectiveRenderLayerMask;
		}
        public float GetAnamorphicRatio()
        { 
			return anamorphicRatio;
		}

        //Bloom
		public MK.Glow.MinMaxRange GetBloomThreshold()
		{ 
			return bloomThreshold;
		}
		public float GetBloomScattering()
		{ 
			return bloomScattering;
		}
		public float GetBloomIntensity()
		{ 
			return bloomIntensity;
		}

        //LensSurface
		public bool GetAllowLensSurface()
		{ 
			return allowLensSurface;
		}
		public Texture2D GetLensSurfaceDirtTexture()
		{ 
			return lensSurfaceDirtTexture;
		}
		public float GetLensSurfaceDirtIntensity()
		{ 
			return lensSurfaceDirtIntensity;
		}
		public Texture2D GetLensSurfaceDiffractionTexture()
		{ 
			return lensSurfaceDiffractionTexture;
		}
		public float GetLensSurfaceDiffractionIntensity()
		{ 
			return lensSurfaceDiffractionIntensity;
		}

        class MKGlowLiteRenderPass : ScriptableRenderPass, MK.Glow.ICameraData
        {
            private MK.Glow.URP.MKGlowLite _mKGlowVolumeComponent;
            private MK.Glow.URP.MKGlowLite mKGlowVolumeComponent
            {
                get
                {
                    _mKGlowVolumeComponent = VolumeManager.instance.stack.GetComponent<MK.Glow.URP.MKGlowLite>();
                    return _mKGlowVolumeComponent;
                }
            }

            internal Effect effect = new Effect();
            internal ScriptableRenderer scriptableRenderer;
            internal MK.Glow.ISettings settingsRendererFeature = null;
            internal Workmode workmode = Workmode.PostProcessVolumes;
            private RenderTarget sourceRenderTarget, destinationRenderTarget;
            private MK.Glow.ISettings _settingsPostProcessingVolume = null;
            private MK.Glow.ISettings _activeSettings = null;
            private RenderTextureDescriptor _sourceDescriptor;
            private readonly int _rendererBufferID = Shader.PropertyToID("_MKGlowLiteScriptableRendererOutput");
            private readonly string _profilerName = "MKGlowLite";
            private Material _copyMaterial;
            private readonly Shader _copyShader = Shader.Find("Hidden/MK/Glow/URP/Copy");
            private readonly int _mainTexID = Shader.PropertyToID("_MainTex");

            public MKGlowLiteRenderPass()
            {
                this.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
                this._copyMaterial = new Material(_copyShader) { hideFlags = HideFlags.HideAndDontSave };
            }

            public void Dispose()
            {
                PipelineExtensions.Destroy(_copyMaterial);
            }

            #if !UNITY_6000_4_OR_NEWER
            #if UNITY_2023_3_OR_NEWER
            [System.Obsolete]
            #endif
            public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
            {
                _sourceDescriptor = cameraTextureDescriptor;
            }
            #endif

            #if !UNITY_6000_4_OR_NEWER
            #if UNITY_2021_3_OR_NEWER
            #if UNITY_2023_3_OR_NEWER
            [System.Obsolete]
            #endif
            public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) 
            {
                base.OnCameraSetup(cmd, ref renderingData);
                #if UNITY_2022_1_OR_NEWER
                    sourceRenderTarget.renderTargetIdentifier = scriptableRenderer.cameraColorTargetHandle;
                #else
                    sourceRenderTarget.renderTargetIdentifier = scriptableRenderer.cameraColorTarget;
                #endif
            }
            #endif
            #endif

            private static Mesh _fullScreenMesh;
            public static Mesh fullscreenMesh
            {
                get
                {
                    if (_fullScreenMesh != null)
                        return _fullScreenMesh;

                    float topV = 1.0f;
                    float bottomV = 0.0f;

                    _fullScreenMesh = new Mesh { name = "Fullscreen Quad" };
                    _fullScreenMesh.SetVertices(new List<Vector3>
                    {
                        new Vector3(-1.0f, -1.0f, 0.0f),
                        new Vector3(-1.0f,  1.0f, 0.0f),
                        new Vector3(1.0f, -1.0f, 0.0f),
                        new Vector3(1.0f,  1.0f, 0.0f)
                    });

                    _fullScreenMesh.SetUVs(0, new List<Vector2>
                    {
                        new Vector2(0.0f, bottomV),
                        new Vector2(0.0f, topV),
                        new Vector2(1.0f, bottomV),
                        new Vector2(1.0f, topV)
                    });

                    _fullScreenMesh.SetIndices(new[] { 0, 1, 2, 2, 1, 3 }, MeshTopology.Triangles, 0, false);
                    _fullScreenMesh.UploadMeshData(true);
                    return _fullScreenMesh;
                }
            }

            #if !UNITY_6000_4_OR_NEWER
            #if UNITY_2023_3_OR_NEWER
            [System.Obsolete]
            #endif
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                if(workmode == Workmode.PostProcessVolumes)
                {
                    if(mKGlowVolumeComponent == null || renderingData.cameraData.camera == null)
                        return;
                    
                    if(!mKGlowVolumeComponent.IsActive())
                        return;
                    }
                else
                {
                    if(renderingData.cameraData.camera == null)
                        return;
                }

                _settingsPostProcessingVolume = mKGlowVolumeComponent;
                _activeSettings = workmode == Workmode.PostProcessVolumes ? _settingsPostProcessingVolume : settingsRendererFeature;

                CommandBuffer cmd = CommandBufferPool.Get(_profilerName);
                if(renderingData.cameraData.camera.allowDynamicResolution)
                {
                    _passData.cameraWidth = Mathf.RoundToInt(renderingData.cameraData.cameraTargetDescriptor.width * ScalableBufferManager.widthScaleFactor);
                    _passData.cameraHeight = Mathf.RoundToInt(renderingData.cameraData.cameraTargetDescriptor.height * ScalableBufferManager.heightScaleFactor);
                }
                else
                {
                    _passData.cameraWidth = renderingData.cameraData.cameraTargetDescriptor.width;
                    _passData.cameraHeight = renderingData.cameraData.cameraTargetDescriptor.height;
                }
            
                #if UNITY_2020_2_OR_NEWER
                    _passData.stereoEnabled = renderingData.cameraData.xrRendering;
                #else
                    _passData.stereoEnabled = renderingData.cameraData.isStereoEnabled;
                #endif
                _passData.aspect = renderingData.cameraData.camera.aspect;
                _passData.worldToCameraMatrix = renderingData.cameraData.camera.worldToCameraMatrix;
                _passData.hasTargetTexture = renderingData.cameraData.camera.targetTexture != null ? true : false;

                #if !UNITY_2021_3_OR_NEWER
                    sourceRenderTarget.renderTargetIdentifier = scriptableRenderer.cameraColorTarget;
                #endif
                
                destinationRenderTarget.identifier = _rendererBufferID;

                #if UNITY_2018_2_OR_NEWER
                destinationRenderTarget.renderTargetIdentifier = new RenderTargetIdentifier(destinationRenderTarget.identifier, 0, CubemapFace.Unknown, -1);
                #else
                destinationRenderTarget.renderTargetIdentifier = new RenderTargetIdentifier(destinationRenderTarget.identifier);
                #endif

                cmd.GetTemporaryRT(destinationRenderTarget.identifier, _sourceDescriptor, FilterMode.Bilinear);
                cmd.SetGlobalTexture(_mainTexID, sourceRenderTarget.renderTargetIdentifier);
                cmd.SetRenderTarget(destinationRenderTarget.renderTargetIdentifier, 0, CubemapFace.Unknown, -1);
                cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, _copyMaterial, 0, 0);
                effect.Build(destinationRenderTarget, sourceRenderTarget, _activeSettings, cmd, this, renderingData.cameraData.camera);
                cmd.ReleaseTemporaryRT(destinationRenderTarget.identifier);

                context.ExecuteCommandBuffer(cmd);

                cmd.Clear();
                CommandBufferPool.Release(cmd);
            }
            #endif

            private PassData _passData = new PassData();
            private class PassData
            {
                #if UNITY_2023_3_OR_NEWER
                internal TextureHandle src;
                internal TextureHandle dest;
                #endif
                internal Workmode workmode = Workmode.PostProcessVolumes;
                internal MK.Glow.URP.MKGlowLite mKGlowVolumeComponent = null;
                #if UNITY_2023_3_OR_NEWER
                public UniversalCameraData universalCameraData = null;
                #endif
                internal MK.Glow.ISettings settingsRendererFeature = null;
                internal MK.Glow.Effect effect;
                internal MK.Glow.URP.MKGlowLiteRendererFeature.MKGlowLiteRenderPass rendererFeaturePass;
                public int mainTexID;
                public Material copyMaterial;

                public int cameraWidth;
                public int cameraHeight;
                public bool stereoEnabled;
                public float aspect;
                public Matrix4x4 worldToCameraMatrix;
                public bool hasTargetTexture;
            }

            #if UNITY_2023_3_OR_NEWER
            static void ExecutePass(PassData data, UnsafeGraphContext context)
            {
                CommandBuffer unsafeCmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
                
                if(data.workmode == Workmode.PostProcessVolumes)
                {
                    if(data.mKGlowVolumeComponent == null || data.universalCameraData.camera == null)
                        return;
                    
                    if(!data.mKGlowVolumeComponent.IsActive())
                        return;
                    }
                else
                {
                    if(data.universalCameraData.camera == null)
                        return;
                }

                MK.Glow.ISettings _activeSettings = data.workmode == Workmode.PostProcessVolumes ? data.mKGlowVolumeComponent : data.settingsRendererFeature;

                CommandBuffer cmd = unsafeCmd;
                RenderTarget sourceRenderTarget = new RenderTarget();
                RenderTarget destinationRenderTarget = new RenderTarget();
                sourceRenderTarget.renderTargetIdentifier = data.src;
                destinationRenderTarget.renderTargetIdentifier = data.dest;
                
                cmd.SetGlobalTexture(data.mainTexID, sourceRenderTarget.renderTargetIdentifier);
                cmd.SetRenderTarget(destinationRenderTarget.renderTargetIdentifier, 0, CubemapFace.Unknown, -1);
                cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, data.copyMaterial, 0, 0);
                data.effect.Build(destinationRenderTarget, sourceRenderTarget, _activeSettings, cmd, data.rendererFeaturePass, data.universalCameraData.camera);
            }

            public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
            {
                string passName = "MK Glow Render Graph";
    
                using (var builder = renderGraph.AddUnsafePass<PassData>(passName, out var passData))
                {
                    UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
                    UniversalCameraData universalCameraData = frameData.Get<UniversalCameraData>();

                    passData.workmode = this.workmode;
                    passData.mKGlowVolumeComponent = this.mKGlowVolumeComponent;
                    passData.universalCameraData = universalCameraData;
                    passData.effect = this.effect;
                    passData.rendererFeaturePass = this;
                    passData.mainTexID = _mainTexID;
                    passData.copyMaterial = _copyMaterial;

                    if(universalCameraData.camera.allowDynamicResolution)
                    {
                        passData.cameraWidth = Mathf.RoundToInt(universalCameraData.cameraTargetDescriptor.width * ScalableBufferManager.widthScaleFactor);
                        passData.cameraHeight = Mathf.RoundToInt(universalCameraData.cameraTargetDescriptor.height * ScalableBufferManager.heightScaleFactor);
                    }
                    else
                    {
                        passData.cameraWidth = universalCameraData.cameraTargetDescriptor.width;
                        passData.cameraHeight = universalCameraData.cameraTargetDescriptor.height;
                    }

                    passData.stereoEnabled = universalCameraData.xrRendering;
                    passData.aspect = universalCameraData.camera.aspect;
                    passData.worldToCameraMatrix = universalCameraData.camera.worldToCameraMatrix;
                    passData.hasTargetTexture = universalCameraData.camera.targetTexture != null ? true : false;
                    passData.settingsRendererFeature = settingsRendererFeature;
    
                    passData.src = resourceData.activeColorTexture;
    
                    RenderTextureDescriptor desc = universalCameraData.cameraTargetDescriptor;
                    desc.depthBufferBits = 0;
    
                    TextureHandle destination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "_MKGlowLiteScriptableRendererRenderGraphOutput", false);
                    passData.dest = destination;
    
                    builder.UseTexture(passData.src);
                    builder.UseTexture(passData.dest);
    
                    builder.AllowPassCulling(false);

                    _passData = passData;
                    builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => ExecutePass(data, context));
                }
            }
            #endif

            /////////////////////////////////////////////////////////////////////////////////////////////
            // Camera Data
            /////////////////////////////////////////////////////////////////////////////////////////////
            public int GetCameraWidth()
            {
                return _passData.cameraWidth;
            }
            public int GetCameraHeight()
            {
                return _passData.cameraHeight;
            }
            public bool GetStereoEnabled()
            {
                return _passData.stereoEnabled;
            }
            public float GetAspect()
            {
                return _passData.aspect;
            }
            public Matrix4x4 GetWorldToCameraMatrix()
            {
                return _passData.worldToCameraMatrix;
            }
            public bool GetOverwriteDescriptor()
            {
                return false;
            }
            public UnityEngine.Rendering.TextureDimension GetOverwriteDimension()
            {
                return UnityEngine.Rendering.TextureDimension.Tex2D;
            }
            public int GetOverwriteVolumeDepth()
            {
                return 1;
            }
            public bool GetTargetTexture()
            {
                return _passData.hasTargetTexture;
            }
        }

        private MKGlowLiteRenderPass _mkGlowRenderPass;
        private readonly string _componentName = "MKGlowLite";

        public override void Create()
        {
            _mkGlowRenderPass = new MKGlowLiteRenderPass();
            _mkGlowRenderPass.effect.Enable(RenderPipeline.SRP);
            _mkGlowRenderPass.settingsRendererFeature = this;
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            name = _componentName;

            _mkGlowRenderPass.workmode = workmode;
            _mkGlowRenderPass.ConfigureInput(ScriptableRenderPassInput.Color);

            if(workmode == Workmode.Global)
            {
                _mkGlowRenderPass.scriptableRenderer = renderer;
                renderer.EnqueuePass(_mkGlowRenderPass);
            }
            else
            {
                if(renderingData.cameraData.postProcessEnabled)
                {
                    _mkGlowRenderPass.scriptableRenderer = renderer;
                    renderer.EnqueuePass(_mkGlowRenderPass);
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            _mkGlowRenderPass?.effect.Disable();
            _mkGlowRenderPass?.Dispose();
            _mkGlowRenderPass = null;
        }
    }
}


