//
// Kino/Bloom v2 - Bloom filter for Unity
//
// Copyright (C) 2015, 2016 Keijiro Takahashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

// Modified by Jean Moreno for Cartoon FX Remaster Demo
// - effect previews in SceneView
// - disabled a code warning

using UnityEngine;

namespace Kino
{
	[ExecuteInEditMode]
	[RequireComponent(typeof(Camera))]
	[ImageEffectAllowedInSceneView]
	public class Bloom : MonoBehaviour
	{
		#region Public Properties

		/// Prefilter threshold (gamma-encoded)
		/// Filters out pixels under this level of brightness.
		public float thresholdGamma
		{
			get { return Mathf.Max(_threshold, 0); }
			set { _threshold = value; }
		}

		/// Prefilter threshold (linearly-encoded)
		/// Filters out pixels under this level of brightness.
		public float thresholdLinear
		{
			get { return GammaToLinear(thresholdGamma); }
			set { _threshold = LinearToGamma(value); }
		}

		[SerializeField]
		[Tooltip("Filters out pixels under this level of brightness.")]
		float _threshold = 0.8f;

		/// Soft-knee coefficient
		/// Makes transition between under/over-threshold gradual.
		public float softKnee
		{
			get { return _softKnee; }
			set { _softKnee = value; }
		}

		[SerializeField, Range(0, 1)]
		[Tooltip("Makes transition between under/over-threshold gradual.")]
		float _softKnee = 0.5f;

		/// Bloom radius
		/// Changes extent of veiling effects in a screen
		/// resolution-independent fashion.
		public float radius
		{
			get { return _radius; }
			set { _radius = value; }
		}

		[SerializeField, Range(1, 7)]
		[Tooltip("Changes extent of veiling effects\n" +
				 "in a screen resolution-independent fashion.")]
		float _radius = 2.5f;

		/// Bloom intensity
		/// Blend factor of the result image.
		public float intensity
		{
			get { return Mathf.Max(_intensity, 0); }
			set { _intensity = value; }
		}

		[SerializeField]
		[Tooltip("Blend factor of the result image.")]
		float _intensity = 0.8f;

		/// High quality mode
		/// Controls filter quality and buffer resolution.
		public bool highQuality
		{
			get { return _highQuality; }
			set { _highQuality = value; }
		}

		[SerializeField]
		[Tooltip("Controls filter quality and buffer resolution.")]
		bool _highQuality = true;

		/// Anti-flicker filter
		/// Reduces flashing noise with an additional filter.
		[SerializeField]
		[Tooltip("Reduces flashing noise with an additional filter.")]
		bool _antiFlicker = true;

		public bool antiFlicker
		{
			get { return _antiFlicker; }
			set { _antiFlicker = value; }
		}

		#endregion

		#region Private Members

#pragma warning disable 0649
		[SerializeField, HideInInspector]
		Shader _shader;
#pragma warning restore 0649

		Material _material;

		const int kMaxIterations = 16;
		RenderTexture[] _blurBuffer1 = new RenderTexture[kMaxIterations];
		RenderTexture[] _blurBuffer2 = new RenderTexture[kMaxIterations];

		float LinearToGamma(float x)
		{
#if UNITY_5_3_OR_NEWER
			return Mathf.LinearToGammaSpace(x);
#else
			if (x <= 0.0031308f)
				return 12.92f * x;
			else
				return 1.055f * Mathf.Pow(x, 1 / 2.4f) - 0.055f;
#endif
		}

		float GammaToLinear(float x)
		{
#if UNITY_5_3_OR_NEWER
			return Mathf.GammaToLinearSpace(x);
#else
			if (x <= 0.04045f)
				return x / 12.92f;
			else
				return Mathf.Pow((x + 0.055f) / 1.055f, 2.4f);
#endif
		}

		#endregion

		#region MonoBehaviour Functions

		void OnEnable()
		{
			var shader = _shader ? _shader : Shader.Find("Hidden/Kino/Bloom");
			_material = new Material(shader);
			_material.hideFlags = HideFlags.DontSave;
		}

		void OnDisable()
		{
			DestroyImmediate(_material);
		}

		void OnRenderImage(RenderTexture source, RenderTexture destination)
		{
			var useRGBM = Application.isMobilePlatform;

			// source texture size
			var tw = source.width;
			var th = source.height;

			// halve the texture size for the low quality mode
			if (!_highQuality)
			{
				tw /= 2;
				th /= 2;
			}

			// blur buffer format
			var rtFormat = useRGBM ?
				RenderTextureFormat.Default : RenderTextureFormat.DefaultHDR;

			// determine the iteration count
			var logh = Mathf.Log(th, 2) + _radius - 8;
			var logh_i = (int)logh;
			var iterations = Mathf.Clamp(logh_i, 1, kMaxIterations);

			// update the shader properties
			var lthresh = thresholdLinear;
			_material.SetFloat("_Threshold", lthresh);

			var knee = lthresh * _softKnee + 1e-5f;
			var curve = new Vector3(lthresh - knee, knee * 2, 0.25f / knee);
			_material.SetVector("_Curve", curve);

			var pfo = !_highQuality && _antiFlicker;
			_material.SetFloat("_PrefilterOffs", pfo ? -0.5f : 0.0f);

			_material.SetFloat("_SampleScale", 0.5f + logh - logh_i);
			_material.SetFloat("_Intensity", intensity);

			// prefilter pass
			var prefiltered = RenderTexture.GetTemporary(tw, th, 0, rtFormat);
			var pass = _antiFlicker ? 1 : 0;
			Graphics.Blit(source, prefiltered, _material, pass);

			// construct a mip pyramid
			var last = prefiltered;
			for (var level = 0; level < iterations; level++)
			{
				_blurBuffer1[level] = RenderTexture.GetTemporary(
					last.width / 2, last.height / 2, 0, rtFormat
				);

				pass = (level == 0) ? (_antiFlicker ? 3 : 2) : 4;
				Graphics.Blit(last, _blurBuffer1[level], _material, pass);

				last = _blurBuffer1[level];
			}

			// upsample and combine loop
			for (var level = iterations - 2; level >= 0; level--)
			{
				var basetex = _blurBuffer1[level];
				_material.SetTexture("_BaseTex", basetex);

				_blurBuffer2[level] = RenderTexture.GetTemporary(
					basetex.width, basetex.height, 0, rtFormat
				);

				pass = _highQuality ? 6 : 5;
				Graphics.Blit(last, _blurBuffer2[level], _material, pass);
				last = _blurBuffer2[level];
			}

			// finish process
			_material.SetTexture("_BaseTex", source);
			pass = _highQuality ? 8 : 7;
			Graphics.Blit(last, destination, _material, pass);

			// release the temporary buffers
			for (var i = 0; i < kMaxIterations; i++)
			{
				if (_blurBuffer1[i] != null)
					RenderTexture.ReleaseTemporary(_blurBuffer1[i]);

				if (_blurBuffer2[i] != null)
					RenderTexture.ReleaseTemporary(_blurBuffer2[i]);

				_blurBuffer1[i] = null;
				_blurBuffer2[i] = null;
			}

			RenderTexture.ReleaseTemporary(prefiltered);
		}

		#endregion
	}
}
