// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if GT_USE_URP
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Microsoft.MixedReality.GraphicsTools
{
///
/// Manages creating and updating blurred background maps for use with the acrylic material.
///
public class AcrylicLayerManager : MonoBehaviour
{
private static AcrylicLayerManager instance;
public static AcrylicLayerManager Instance
{
get { return instance; }
}
[Experimental]
[Tooltip("Whether this platforms supports creating a blurred acrylic map")]
[SerializeField]
private bool acrylicSupported = true;
public bool AcrylicSupported
{
get { return acrylicSupported; }
set { acrylicSupported = value; }
}
public enum AcrylicMethod { CopyFramebuffer, RenderToTexture }
[Tooltip("Capture method for background image")]
[SerializeField]
private AcrylicMethod captureMethod = AcrylicMethod.CopyFramebuffer;
[Tooltip("Use 16-bit or 24-bit depth buffer")]
[SerializeField]
private bool _24BitDepthBuffer = false;
public bool UseOnlyMainCamera
{
get { return useOnlyMainCamera; }
private set { useOnlyMainCamera = value; }
}
[Tooltip("When true the targetCamera is always updated to be the camera tagged as MainCamera.")]
[SerializeField]
private bool useOnlyMainCamera = false;
[Tooltip("Which camera to add the blur pass(es) to. None applies to all cameras. Setting UseOnlyMainCamera to true will overwrite this.")]
[SerializeField]
private Camera targetCamera = null;
public int RendererIndex
{
get { return rendererIndex; }
set
{
if (initialized)
{
Debug.LogWarning("Failed to set the render index because the layer manager is already initialized.");
return;
}
rendererIndex = value;
}
}
[Tooltip("Which renderer to use in the UniversalRenderPipelineAsset.")]
[SerializeField]
private int rendererIndex = 0;
public enum BlurMethod { Kawase, Dual }
[Header("Blur")]
[SerializeField]
private BlurMethod filterMethod = BlurMethod.Kawase;
public BlurMethod FilterMethod
{
get { return filterMethod; }
set
{
if (initialized)
{
Debug.LogWarning("Failed to set the filter method because the layer manager is already initialized.");
return;
}
filterMethod = value;
}
}
[Tooltip("Material for kawase blur")]
[SerializeField]
private Material kawaseFilterMaterial = null;
public Material KawaseFilterMaterial
{
get { return kawaseFilterMaterial; }
private set { kawaseFilterMaterial = value; }
}
[Tooltip("Material for dual blur filter")]
[SerializeField]
private Material dualFilterMaterial = null;
public Material DualFilterMaterial
{
get { return dualFilterMaterial; }
private set { dualFilterMaterial = value; }
}
[Header("Blur Map Update Options")]
[Tooltip("Whether to automatically update blur map")]
[SerializeField]
private bool autoUpdateBlurMap = true;
public bool AutoUpdateBlurMap
{
get { return autoUpdateBlurMap; }
set { autoUpdateBlurMap = value; }
}
[Tooltip("How often to record a new background image for the blur map")]
[SerializeField]
[Range(1, 60)]
private int updatePeriod = 1;
[Tooltip("Frames over which to blend from old to new blur map.")]
[SerializeField]
[Range(0, 60)]
private int blendFrames = 0;
[Tooltip("Material used when blending between old and new blur maps.")]
[SerializeField]
private Material blendMaterial = null;
[SerializeField]
private List layers;
public List Layers
{
get { return layers; }
private set { layers = value; }
}
#region private properties
private List layerData = new List();
#if UNITY_2021_2_OR_NEWER
private UniversalRendererData rendererData = null;
#else
private ForwardRendererData rendererData = null;
#endif
private bool initialized = false;
private bool acrylicActive = true;
private const string namePrefix = "AcrylicBlur";
private bool ExecuteBeforeRenderAdded = false;
private Coroutine updateRoutine = null;
private IntermediateTextureMode previousIntermediateTextureMode;
#endregion
#region Monobehavior methods
private void OnDestroy()
{
// Reset the intermediate texture mode.
if (rendererData != null)
{
rendererData.intermediateTextureMode = previousIntermediateTextureMode;
}
switch (captureMethod)
{
case AcrylicMethod.CopyFramebuffer:
RemoveAllLayers();
break;
case AcrylicMethod.RenderToTexture:
if (ExecuteBeforeRenderAdded)
{
RenderPipelineManager.beginCameraRendering -= ExecuteBeforeCameraRender;
}
break;
default:
break;
}
for (int i = 0; i < layerData.Count; ++i)
{
layerData[i].Dispose();
}
layerData.Clear();
}
private void Awake()
{
if (instance != null && instance != this)
{
Debug.LogErrorFormat("An instance of the AcrylicLayerManager already exists on gameobject {0}", instance.name);
return;
}
instance = this;
InitializeBlurTexturesToBlack();
}
private void Start()
{
if (AcrylicSupported)
{
Initialize();
}
}
private void OnValidate()
{
InitializeBlurTexturesToBlack();
}
#endregion
#region Public methods
public void EnableLayer(int i)
{
if (!AcrylicSupported) return;
Initialize();
if (i >= 0 && i < layerData.Count)
{
layerData[i].activeCount++;
if (layerData[i].activeCount == 1)
{
layerData[i].frameCount = 0;
layerData[i].firstFrameRendered = false;
if (captureMethod == AcrylicMethod.CopyFramebuffer)
{
UpdateActiveLayers();
}
StartUpdateRoutine();
}
}
}
public void DisableLayer(int i)
{
if (!AcrylicSupported) return;
Initialize();
if (i >= 0 && i < layerData.Count && layerData[i].activeCount > 0)
{
layerData[i].activeCount--;
if (layerData[i].activeCount == 0 && rendererData != null)
{
UpdateActiveLayers();
}
}
}
public bool LayerVisible(int i)
{
return (i >= 0 && i < layerData.Count && layerData[i].activeCount > 0 && rendererData != null);
}
public bool AcrylicActive
{
get
{
return acrylicActive && AcrylicSupported;
}
set
{
if (value != acrylicActive)
{
acrylicActive = value;
if (AcrylicSupported)
{
UpdateActiveLayers();
}
}
}
}
public void SetTargetCamera(Camera newtargetCamera)
{
if (targetCamera != newtargetCamera)
{
targetCamera = newtargetCamera;
foreach (AcrylicLayer layer in layerData)
{
layer.SetTargetCamera(newtargetCamera);
}
}
}
#endregion
#region private methods
private void Initialize()
{
if (!initialized && AcrylicSupported)
{
initialized = true;
rendererData = URPUtility.GetRendererData(rendererIndex);
if (rendererData != null)
{
RemoveExistingAcrylicPasses();
// Previously, URP would force rendering to go through an intermediate renderer if the Renderer had any Renderer Features active. On some platforms, this has
// significant performance implications so we only want to enable this when we need it (which we do for magnification).
previousIntermediateTextureMode = rendererData.intermediateTextureMode;
rendererData.intermediateTextureMode = IntermediateTextureMode.Always;
}
CreateLayers();
if (captureMethod == AcrylicMethod.RenderToTexture)
{
ExecuteBeforeRenderAdded = true;
RenderPipelineManager.beginCameraRendering += ExecuteBeforeCameraRender;
}
}
}
private void InitializeBlurTexturesToBlack()
{
if (layers != null)
{
foreach (AcrylicLayer.Settings layer in layers)
{
if (!string.IsNullOrEmpty(layer.blurTextureName))
{
Shader.SetGlobalTexture(layer.blurTextureName, Texture2D.blackTexture);
}
}
}
}
private void CreateLayers()
{
for (int i = 0; i < layers.Count; i++)
{
layerData.Add(CreateLayer(layers[i], i));
}
}
private void AddAllLayers()
{
if (rendererData != null)
{
for (int i = 0; i < layerData.Count; i++)
{
EnableLayer(i);
}
}
}
private AcrylicLayer CreateLayer(AcrylicLayer.Settings settings, int index)
{
AcrylicLayer layer = new AcrylicLayer(targetCamera, settings, index, _24BitDepthBuffer ? 24 : 16, filterMethod == BlurMethod.Dual, kawaseFilterMaterial, dualFilterMaterial);
if (captureMethod == AcrylicMethod.CopyFramebuffer)
{
layer.CreateRendererFeatures();
}
return layer;
}
private void UpdateActiveLayers()
{
RemoveAllLayers();
if (AcrylicActive)
AddActiveLayers();
}
private void RemoveExistingAcrylicPasses()
{
List passes = rendererData.rendererFeatures;
for (int i = passes.Count - 1; i >= 0; i--)
{
if (passes[i].name.Contains("Acrylic"))
{
#if (UNITY_EDITOR)
UnityEditor.AssetDatabase.RemoveObjectFromAsset(passes[i]);
#endif
rendererData.rendererFeatures.Remove(passes[i]);
}
}
}
private bool AnyLayersNeedUpdating()
{
for (int i = 0; i < layerData.Count; i++)
{
if (layerData[i].activeCount > 0)
{
if (autoUpdateBlurMap || !layerData[i].FirstFrameGenerated) return true;
}
}
return false;
}
#endregion
#region Render to texture methods
private void ExecuteBeforeCameraRender(ScriptableRenderContext context, Camera camera)
{
if (captureMethod == AcrylicMethod.RenderToTexture)
{
for (int i = 0; i < layerData.Count; i++)
{
if (layerData[i].activeCount > 0 && layerData[i].frameCount == 0)
{
layerData[i].RenderToTexture(context, camera, updatePeriod, blendFrames, CumulativeLayerMask(i));
}
}
}
}
// returns the union of the current layer mask and all layer masks 'above' it
private LayerMask CumulativeLayerMask(int layer)
{
LayerMask mask = layers[layer].renderLayers;
for (int i = layer + 1; i < layers.Count; i++)
{
mask |= layers[i].renderLayers;
}
return mask;
}
#endregion
#region Periodic update methods
private void StartUpdateRoutine()
{
if (updateRoutine == null)
{
updateRoutine = StartCoroutine(UpdateRoutine());
}
}
private IEnumerator UpdateRoutine()
{
while (AnyLayersNeedUpdating())
{
bool updateActiveFeatures = false;
for (int i = 0; i < layerData.Count; i++)
{
if (layerData[i].activeCount > 0)
{
if (UseOnlyMainCamera)
{
layerData[i].SetTargetCamera(Camera.main);
}
layerData[i].UpdateFrame(rendererData, captureMethod == AcrylicMethod.CopyFramebuffer, updatePeriod, blendFrames, blendMaterial, autoUpdateBlurMap);
if (captureMethod==AcrylicMethod.CopyFramebuffer)
{
bool inList = layerData[i].InFeaturesList(rendererData);
if (layerData[i].CaptureNextFrame)
{
if (!inList) updateActiveFeatures = true;
if (updatePeriod == 1 && autoUpdateBlurMap) layerData[i].ForceCaptureNextFrame(); //needed if updatePeriod changed in editor
}
else
{
if (inList) updateActiveFeatures = true;
}
}
}
}
if (updateActiveFeatures) UpdateActiveLayers();
yield return null;
}
updateRoutine = null;
}
#endregion
#region Copy framebuffer related methods
private void AddActiveLayers()
{
if (captureMethod != AcrylicMethod.CopyFramebuffer) return;
for (int i = 0; i < layerData.Count; i++)
{
if (layerData[i].activeCount > 0 && layerData[i].CaptureNextFrame)
{
layerData[i].AddLayerRendererFeatures(rendererData, updatePeriod < 2 && autoUpdateBlurMap);
}
}
}
private void RemoveAllLayers()
{
if (captureMethod != AcrylicMethod.CopyFramebuffer) return;
for (int i = 0; i < layerData.Count; i++)
{
layerData[i].RemoveLayerRendererFeatures(rendererData);
}
}
#endregion
}
}
#endif // GT_USE_URP