// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#if GT_USE_URP
using System;
using UnityEngine;
using UnityEngine.Experimental.Rendering.Universal;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
/// TODO - [Cameron-Micka] remove obsolete API.
#pragma warning disable 0618
namespace Microsoft.MixedReality.GraphicsTools
{
///
/// Class representing a single acrylic layer, used in conjuction with AcrylicLayerManager
///
public class AcrylicLayer : IDisposable
{
[System.Serializable]
public class Settings
{
[Tooltip("When to copy the framebuffer in the rendering pipeline. No effect when render-to-texture is used.")]
public RenderPassEvent captureEvent = RenderPassEvent.AfterRenderingPostProcessing;
[Tooltip("If not nothing, creates render object features for the specified layers.")]
public LayerMask renderLayers;
[Range(2, 7)]
public int blurPasses = 5;
[Range(0, 2)]
public int downSample = 2;
public string blurTextureName;
[Header("Advanced")]
[Tooltip("The name of the render feature to add the blur after (or before) to enforce custom sorting. Adds to the end of the render feature list when empty.")]
public string targetRenderFeatureName = string.Empty;
public enum AddMode { After, Before }
[Tooltip("When a Target Render Feature Name is specified, should it be added before or after the feature in the list?")]
public AddMode targetRenderFeatureAddMode = AddMode.After;
}
public int activeCount;
public int frameCount;
public bool firstFrameRendered;
private Camera targetCamera;
private AcrylicBlurFeature blur;
private ScriptableRendererFeature renderOpaque;
private ScriptableRendererFeature renderTransparent;
private RenderTexture renderTarget1;
private RenderTexture renderTarget2;
private RenderTexture[] blendSource;
private int blendSourceIndex;
private RenderTexture blendTarget;
private bool blurred;
private bool immediateBlur;
private Settings settings;
private int index;
private int depthBits;
private Material kawaseBlur;
private bool useDualBlur;
private AcrylicFilterDual dualBlur = null;
private static CommandBuffer cmd = null;
#region Public methods
public AcrylicLayer(Camera _targetCamera, Settings _settings, int _index, int _depthBits, bool _useDualBlur, Material _kawaseBlur, Material _dualBlur)
{
activeCount = 0;
frameCount = 0;
targetCamera = _targetCamera;
renderTarget1 = null;
renderTarget2 = null;
blendTarget = null;
blendSource = new RenderTexture[2];
blendSource[0] = null;
blendSource[1] = null;
blendSourceIndex = 0;
firstFrameRendered = false;
blurred = false;
immediateBlur = false;
settings = _settings;
index = _index;
depthBits = _depthBits;
useDualBlur = _useDualBlur;
kawaseBlur = _kawaseBlur;
if (useDualBlur) dualBlur = new AcrylicFilterDual(_dualBlur);
}
public void Dispose()
{
if (blur != null)
{
DestroyScriptableObject(blur);
blur = null;
}
if (renderOpaque != null)
{
DestroyScriptableObject(renderOpaque);
renderOpaque = null;
}
if (renderTransparent != null)
{
DestroyScriptableObject(renderTransparent);
renderTransparent = null;
}
if (dualBlur != null)
{
dualBlur.Dispose();
dualBlur = null;
}
}
#if UNITY_2021_2_OR_NEWER
public void AddLayerRendererFeatures(UniversalRendererData rendererData, bool updateEveryFrame)
#else
public void AddLayerRendererFeatures(ForwardRendererData rendererData, bool updateEveryFrame)
#endif
{
if (rendererData == null) return;
int insertIndex = -1;
if (!string.IsNullOrEmpty(settings.targetRenderFeatureName))
{
insertIndex = rendererData.rendererFeatures.FindIndex(x => x.name == settings.targetRenderFeatureName);
}
if (insertIndex == -1)
{
insertIndex = rendererData.rendererFeatures.Count - 1;
}
if (settings.targetRenderFeatureAddMode == Settings.AddMode.After)
{
++insertIndex;
}
rendererData.rendererFeatures.Insert(insertIndex, blur);
if (renderOpaque != null)
{
++insertIndex;
rendererData.rendererFeatures.Insert(insertIndex, renderOpaque);
rendererData.opaqueLayerMask = rendererData.opaqueLayerMask & ~settings.renderLayers;
}
if (renderTransparent != null)
{
++insertIndex;
rendererData.rendererFeatures.Insert(insertIndex, renderTransparent);
rendererData.transparentLayerMask = rendererData.transparentLayerMask & ~settings.renderLayers;
}
blur.SetMaterialTexture(!firstFrameRendered || updateEveryFrame);
immediateBlur = !firstFrameRendered || updateEveryFrame;
blur.ApplyBlur(immediateBlur);
RenderTexture captureTarget = null;
if (!updateEveryFrame || useDualBlur)
{
if (renderTarget1 == null) InitRenderTargets(1, 1, depthBits);
captureTarget = renderTarget1;
}
blur.SetStorageTexture(captureTarget);
blur.rendered = false;
blurred = false;
rendererData.SetDirty();
}
public void ForceCaptureNextFrame()
{
if (blur!=null)
{
if (!useDualBlur) blur.SetStorageTexture(null);
blur.SetMaterialTexture(true);
blur.rendered = false;
blur.ApplyBlur(true);
blur.blur = settings.blurPasses;
blur.downSample = settings.downSample;
immediateBlur = true;
}
}
#if UNITY_2021_2_OR_NEWER
public void RemoveLayerRendererFeatures(UniversalRendererData rendererData)
#else
public void RemoveLayerRendererFeatures(ForwardRendererData rendererData)
#endif
{
if (rendererData == null) return;
rendererData.rendererFeatures.Remove(blur);
if (renderOpaque != null)
{
LayerMask opaqueMask = rendererData.opaqueLayerMask;
rendererData.rendererFeatures.Remove(renderOpaque);
opaqueMask |= settings.renderLayers;
rendererData.opaqueLayerMask = opaqueMask;
}
if (renderTransparent != null)
{
LayerMask transparentMask = rendererData.transparentLayerMask;
rendererData.rendererFeatures.Remove(renderTransparent);
transparentMask |= settings.renderLayers;
rendererData.transparentLayerMask = transparentMask;
}
rendererData.SetDirty();
}
public void SwapRenderTargets()
{
var swap = renderTarget1;
renderTarget1 = renderTarget2;
renderTarget2 = swap;
}
public int DownSamplePowerOf2 => PowerOf2(settings.downSample);
public bool CaptureNextFrame => frameCount == 0;
public void CreateRendererFeatures()
{
blur = CreateBlurFeature("Acrylic Blur" + index, settings.captureEvent, kawaseBlur, targetCamera);
if (settings.renderLayers.value != 0)
{
renderOpaque = CreateRenderObjectsFeature("Post Acrylic Blur Opaque " + index, RenderQueueType.Opaque, settings.captureEvent);
renderTransparent = CreateRenderObjectsFeature("Post Acrylic Blur Transparent " + index, RenderQueueType.Transparent, settings.captureEvent);
}
}
public bool FirstFrameGenerated => firstFrameRendered && blurred;
#if UNITY_2021_2_OR_NEWER
public void UpdateFrame(UniversalRendererData rendererData, bool copyFramebuffer, int updatePeriod, int blendFrames, Material blendMaterial, bool autoUpdate)
#else
public void UpdateFrame(ForwardRendererData rendererData, bool copyFramebuffer, int updatePeriod, int blendFrames, Material blendMaterial, bool autoUpdate)
#endif
{
if (!firstFrameRendered && copyFramebuffer && blur.rendered)
{
firstFrameRendered = true;
if (blendFrames > 0 && updatePeriod>1 && renderTarget1!=null && autoUpdate)
SetBlendSource(renderTarget1, true);
}
if (firstFrameRendered)
{
if (!blurred && copyFramebuffer && blur.rendered)
{
blurred = immediateBlur;
if (blurred)
SwapRenderTargets();
}
if (!blurred && updatePeriod > 1 && autoUpdate)
{
ApplyBlur();
if (blendFrames > 0)
{
SetBlendSource(renderTarget1, false);
}
else
{
Shader.SetGlobalTexture(settings.blurTextureName, renderTarget1);
SwapRenderTargets();
}
blurred = true;
}
if (blendMaterial != null && blendFrames > 0)
{
float blend = Mathf.Clamp01((float)frameCount / blendFrames);
BlendLayer(blend, blendMaterial);
}
frameCount = (frameCount + 1);
if (autoUpdate)
{
frameCount = frameCount % updatePeriod;
}
}
}
#if UNITY_2021_2_OR_NEWER
public bool InFeaturesList(UniversalRendererData rendererData)
#else
public bool InFeaturesList(ForwardRendererData rendererData)
#endif
{
return rendererData != null && blur != null && rendererData.rendererFeatures.Contains(blur);
}
public void RenderToTexture(ScriptableRenderContext context, Camera mainCamera, int updatePeriod, int blendFrames, LayerMask hiddenLayers)
{
Profiler.BeginSample("AcrylicLayer" + index + "_RenderToTexture");
int camWidth = mainCamera.scaledPixelWidth;
int camHeight = mainCamera.scaledPixelHeight;
int downSample = DownSamplePowerOf2;
int newWidth = camWidth / downSample;
int newHeight = camHeight / downSample;
Vector2 pixelSize = new Vector2(1.0f / newWidth, 1.0f / newHeight);
InitRenderTargets(newWidth, newHeight, depthBits);
LayerMask previousCullingMask = mainCamera.cullingMask;
RenderTexture previousTargetTexture = mainCamera.targetTexture;
mainCamera.cullingMask = previousCullingMask & (~hiddenLayers);
mainCamera.targetTexture = renderTarget1;
UnityEngine.Rendering.Universal.UniversalRenderPipeline.RenderSingleCamera(context, mainCamera);
mainCamera.cullingMask = previousCullingMask;
mainCamera.targetTexture = previousTargetTexture;
blurred = updatePeriod < 2 || !firstFrameRendered;
if (blurred)
{
ApplyBlur();
if (blendFrames > 0)
{
SetBlendSource(renderTarget1, !firstFrameRendered);
}
else
{
Shader.SetGlobalTexture(settings.blurTextureName, renderTarget1);
SwapRenderTargets();
}
}
if (!firstFrameRendered)
firstFrameRendered = true;
Profiler.EndSample();
}
#if UNITY_2021_2_OR_NEWER
public void MakeFeaturesPersistent(UniversalRendererData rendererData)
#else
public void MakeFeaturesPersistent(ForwardRendererData rendererData)
#endif
{
MakeFeaturePersistent(rendererData, blur);
if (renderOpaque != null)
{
MakeFeaturePersistent(rendererData, renderOpaque);
}
if (renderTransparent != null)
{
MakeFeaturePersistent(rendererData, renderTransparent);
}
}
#endregion
#region Blur and blend methods
public void ApplyBlur()
{
ApplyBlur(ref renderTarget1, ref renderTarget2);
}
public void ApplyBlur(ref RenderTexture source, ref RenderTexture destination)
{
if (source == null)
{
Debug.LogWarning("Null blur source texture.");
return;
}
if (useDualBlur)
{
dualBlur.ApplyBlur("AcrylicLayer" + index + "_Blur", source, settings.blurPasses);
}
else
{
InitRenderTexture(ref destination, source.width, source.height, depthBits, "Destination");
InitCommandBuffer();
Profiler.BeginSample("AcrylicLayer" + index + "_Blur");
cmd.Clear();
int width = source.width;
int height = source.height;
Vector2 pixelSize = new Vector2(1.0f / width, 1.0f / height);
float[] widths = AcrylicBlurRenderPass.BlurWidths(settings.blurPasses);
for (int i = 0; i < widths.Length; i++)
{
cmd.SetGlobalVector("_AcrylicBlurOffset", (0.5f + widths[i]) * pixelSize);
LocalBlit(cmd, source, destination, kawaseBlur);
var swap = source;
source = destination;
destination = swap;
}
Graphics.ExecuteCommandBuffer(cmd);
Profiler.EndSample();
}
}
public void SetBlendSource(RenderTexture input, bool both)
{
InitRenderTexture(ref blendSource[blendSourceIndex], input.width, input.height, 0, "BlendSource");
InitCommandBuffer();
cmd.Clear();
cmd.Blit(input, blendSource[blendSourceIndex]);
if (both)
{
InitRenderTexture(ref blendSource[1-blendSourceIndex], input.width, input.height, 0, "BlendSource");
cmd.Blit(input, blendSource[1 - blendSourceIndex]);
}
Graphics.ExecuteCommandBuffer(cmd);
blendSourceIndex = 1 - blendSourceIndex;
}
public void BlendLayer(float blend, Material blendMaterial)
{
if (blendSource[0] == null || blendSource[1] == null) return;
int src0 = blendSourceIndex;
int src1 = 1 - blendSourceIndex;
if (blend <= 0.0f)
{
Shader.SetGlobalTexture(settings.blurTextureName, blendSource[src0]);
}
else if (blend >= 1.0f)
{
Shader.SetGlobalTexture(settings.blurTextureName, blendSource[src1]);
}
else
{
InitRenderTexture(ref blendTarget, blendSource[src1].width, blendSource[src1].height, 0, "BlendTarget");
InitCommandBuffer();
cmd.Clear();
cmd.SetGlobalTexture("_AcrylicBlendTexture", blendSource[src1]);
cmd.SetGlobalFloat("_AcrylicBlendFraction", blend);
cmd.Blit(blendSource[src0], blendTarget, blendMaterial);
Graphics.ExecuteCommandBuffer(cmd);
Shader.SetGlobalTexture(settings.blurTextureName, blendTarget);
}
}
public void SetTargetCamera(Camera newtargetCamera)
{
targetCamera = newtargetCamera;
if (blur != null)
{
blur.targetCamera = newtargetCamera;
}
}
#endregion
#region private methods
private void InitCommandBuffer()
{
if (cmd == null)
{
cmd = new CommandBuffer();
cmd.name = "AcrylicBlurPasses";
}
}
private int PowerOf2(int i)
{
int powerOf2 = 1;
while (i > 0)
{
i--;
powerOf2 *= 2;
}
return powerOf2;
}
private void InitRenderTargets(int newWidth, int newHeight, int depth)
{
InitRenderTexture(ref renderTarget1, newWidth, newHeight, depth, "RenderTarget1");
InitRenderTexture(ref renderTarget2, newWidth, newHeight, depth, "RenderTarget2");
}
private static void InitRenderTexture(ref RenderTexture texture, int newWidth, int newHeight, int depth, string name)
{
if (texture == null)
{
texture = new RenderTexture(newWidth, newHeight, depth, RenderTextureFormat.ARGB32);
texture.name = name;
}
else if (texture.width != newWidth || texture.height != newHeight)
{
texture.Release();
texture.width = newWidth;
texture.height = newHeight;
texture.Create();
}
}
private AcrylicBlurFeature CreateBlurFeature(string name, RenderPassEvent blurMapCreation, Material blurMaterial, Camera targetCamera)
{
AcrylicBlurFeature blur = null;
if (blurMaterial != null)
{
blur = ScriptableObject.CreateInstance();
blur.name = name;
blur.renderPassEvent = blurMapCreation;
blur.blur = settings.blurPasses;
blur.downSample = settings.downSample;
blur.textureName = settings.blurTextureName;
blur.blurMaterial = blurMaterial;
blur.targetCamera = targetCamera;
if (useDualBlur) blur.SetBlurMethod(dualBlur);
}
return blur;
}
private ScriptableRendererFeature CreateRenderObjectsFeature(string name, RenderQueueType queue, RenderPassEvent blurMapCreation)
{
RenderObjects r = ScriptableObject.CreateInstance();
r.name = name;
r.settings.Event = blurMapCreation;
r.settings.filterSettings.RenderQueueType = queue;
r.settings.filterSettings.LayerMask = settings.renderLayers;
return r;
}
#if UNITY_2021_2_OR_NEWER
private void MakeFeaturePersistent(UniversalRendererData rendererData, ScriptableRendererFeature feature)
#else
private void MakeFeaturePersistent(ForwardRendererData rendererData, ScriptableRendererFeature feature)
#endif
{
#if UNITY_EDITOR
if (rendererData != null && feature != null)
{
UnityEditor.AssetDatabase.AddObjectToAsset(feature, rendererData);
UnityEditor.AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out var guide, out long localId);
}
#endif
}
private MaterialPropertyBlock blitProperties = new MaterialPropertyBlock();
private void LocalBlit(CommandBuffer cmd, RenderTexture source, RenderTexture target, Material material)
{
cmd.SetRenderTarget(target);
blitProperties.SetTexture("_AcrylicBlurSource", source);
//cmd.SetGlobalTexture("_MainTex", source);
cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, material, 0, 0, blitProperties);
}
private void DestroyScriptableObject(UnityEngine.Object o)
{
if (Application.isPlaying)
{
ScriptableObject.Destroy(o);
}
else
{
ScriptableObject.DestroyImmediate(o);
}
}
#endregion
}
}
#endif // GT_USE_URP