using System;
using System.Collections.Generic;
using Coffee.UISoftMaskInternal;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.UI;
#if URP_ENABLE
using UnityEngine.Rendering.Universal;
#endif
namespace Coffee.UISoftMask
{
///
/// SoftMask.
///
[DisallowMultipleComponent]
[ExecuteAlways]
[Icon("Packages/com.coffee.softmask-for-ugui/Icons/SoftMaskIcon.png")]
public class SoftMask : Mask, ISoftMasking, IMaskable, IMaskingShapeContainerOwner, ISerializationCallbackReceiver
{
///
/// Down sampling rate.
///
public enum DownSamplingRate
{
None = 0,
x1 = 1,
x2 = 2,
x4 = 4,
x8 = 8
}
///
/// Masking mode.
/// SoftMasking: Use soft masking.
/// AntiAliasing: Use anti-aliasing.
///
public enum MaskingMode
{
SoftMasking,
AntiAliasing,
Normal
}
public static Action onRenderSoftMaskBuffer = null;
private static readonly Camera.MonoOrStereoscopicEye[] s_MonoEyes = { Camera.MonoOrStereoscopicEye.Mono };
private static readonly Camera.MonoOrStereoscopicEye[] s_StereoEyes =
{ Camera.MonoOrStereoscopicEye.Left, Camera.MonoOrStereoscopicEye.Right };
[Tooltip("Masking mode.\n\n" +
"SoftMasking: Use RenderTexture as a soft mask buffer. The alpha of the masking graphic can be used.\n" +
"AntiAliasing: Suppress the jaggedness of the masking graphic. The masking graphic cannot be displayed.\n" +
"Normal: Same as Mask component's stencil mask.")]
[SerializeField]
private MaskingMode m_MaskingMode = MaskingMode.SoftMasking;
[Tooltip("The transparent part of the mask cannot be clicked.\n" +
"This can be achieved by enabling Read/Write enabled in the Texture Import Settings for the texture.\n\n" +
"NOTE: Enable this only if necessary, as it will require more graphics memory and processing time.")]
[SerializeField]
private bool m_AlphaHitTest;
[Tooltip("The minimum and maximum alpha values used for soft masking.\n" +
"The larger the gap between these values, the stronger the softness effect.")]
[SerializeField]
private MinMax01 m_SoftnessRange = new MinMax01(0, 1f);
[Tooltip("The down sampling rate for soft mask buffer.\n" +
"The higher this value, the lower the quality of the soft masking, but the performance will improve.")]
[SerializeField]
private DownSamplingRate m_DownSamplingRate = DownSamplingRate.x1;
[Tooltip("The threshold for anti-alias masking.\n" +
"The smaller this value, the less jagged it is.")]
[SerializeField]
[Range(0f, 1f)]
private float m_AntiAliasingThreshold;
[SerializeField]
[Obsolete]
internal float m_Alpha = -1;
[SerializeField]
[Obsolete]
internal float m_Softness = -1;
[SerializeField]
[Obsolete]
private bool m_PartOfParent;
private CanvasGroup _canvasGroup;
private CommandBuffer _cb;
private List _children;
private bool _hasResolutionChanged;
private bool _hasSoftMaskBufferDrawn;
private Mesh _mesh;
private MaterialPropertyBlock _mpb;
private Action _onBeforeCanvasRebuild;
private Action _onCanvasViewChanged;
private SoftMask _parent;
private Matrix4x4 _prevTransformMatrix;
private Action _renderSoftMaskBuffer;
private Canvas _rootCanvas;
private UnityAction _setSoftMaskDirty;
private MaskingShapeContainer _shapeContainer;
internal RenderTexture _softMaskBuffer;
private UnityAction _updateParentSoftMask;
private CanvasViewChangeTrigger _viewChangeTrigger;
private List children =>
_children != null ? _children : _children = InternalListPool.Rent();
public MaskingShape.MaskingMethod maskingMethod => MaskingShape.MaskingMethod.Additive;
public MaskingShape.RaycastMethod raycastMethod => MaskingShape.RaycastMethod.Additive;
///
/// Masking mode.
/// SoftMasking: Use RenderTexture as a soft mask buffer. The alpha of the masking graphic can be used.
/// AntiAliasing: Suppress the jaggedness of the masking graphic. The masking graphic cannot be displayed.
/// Normal: Same as Mask component's stencil mask.
///
public MaskingMode maskingMode
{
get => m_MaskingMode;
set
{
if (m_MaskingMode == value) return;
m_MaskingMode = value;
AddSoftMaskableOnChildren();
UpdateAntiAlias();
SetDirtyAndNotify();
if (graphic)
{
graphic.SetMaterialDirty();
}
}
}
public DownSamplingRate downSamplingRate
{
get => m_DownSamplingRate;
set
{
if (m_DownSamplingRate == value) return;
m_DownSamplingRate = value;
SetDirtyAndNotify();
}
}
///
/// Threshold for anti-alias masking.
/// The smaller this value, the less jagged it is.
///
public float antiAliasingThreshold
{
get => m_AntiAliasingThreshold;
set => m_AntiAliasingThreshold = value;
}
///
/// The transparent part of the mask cannot be clicked.
/// This can be achieved by enabling Read/Write enabled in the Texture Import Settings for the texture.
///
/// NOTE: Enable this only if necessary, as it will require more graphics memory and processing time.
///
public bool alphaHitTest
{
get => m_AlphaHitTest;
set => m_AlphaHitTest = value;
}
///
/// The soft mask depth.
///
public int softMaskDepth
{
get
{
var depth = -1;
for (var current = this; current; current = current._parent)
{
if (current.SoftMaskingEnabled())
{
depth++;
}
}
return depth;
}
}
///
/// Is the soft mask a part of parent soft mask?
///
[Obsolete("Use MaskingShape component instead.", false)]
public bool partOfParent
{
get => m_PartOfParent;
set => m_PartOfParent = value;
}
///
/// The value used by the soft mask to select the area of influence defined over the soft mask's graphic.
///
[Obsolete("Use softnessRange instead.", false)]
public float softness
{
get => softnessRange.max;
set
{
softnessRange = new MinMax01(0, Mathf.Clamp01(value));
m_Softness = -1;
}
}
public bool hasSoftMaskBuffer => _softMaskBuffer;
///
/// The soft mask buffer.
///
public RenderTexture softMaskBuffer
{
get
{
if (SoftMaskingEnabled())
{
var size = RenderTextureRepository.GetScreenSize((int)downSamplingRate);
var hash = new Hash128((uint)GetInstanceID(), (uint)size.x, (uint)size.y, 0);
if (!RenderTextureRepository.Valid(hash, _softMaskBuffer))
{
RenderTextureRepository.Get(hash, ref _softMaskBuffer,
x => new RenderTexture(RenderTextureRepository.GetDescriptor(x, false))
{
hideFlags = HideFlags.DontSave
}, size);
}
return _softMaskBuffer;
}
RenderTextureRepository.Release(ref _softMaskBuffer);
return null;
}
}
///
/// The minimum and maximum alpha values used for soft masking.
/// The larger the gap between these values, the stronger the softness effect.
///
public MinMax01 softnessRange
{
get => m_SoftnessRange;
set
{
if (m_SoftnessRange.Approximately(value)) return;
m_SoftnessRange = value;
SetSoftMaskDirty();
}
}
///
/// Clear color for the soft mask buffer.
///
public Color clearColor
{
get;
set;
}
public bool isDirty
{
get;
private set;
}
public bool allowRenderScale
{
get
{
#if URP_ENABLE
if (_rootCanvas
&& _rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay
&& _rootCanvas.worldCamera
&& GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset)
{
return true;
}
#endif
return false;
}
}
public bool allowDynamicResolution
{
get
{
var isSupported =
#if UNITY_XBOXONE || UNITY_PS5 || UNITY_PS4 || UNITY_SWITCH || UNITY_IOS || UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
true;
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_TVOS
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal;
#elif UNITY_ANDROID
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan;
#elif UNITY_WSA
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12;
#else
false;
#endif
return isSupported
&& _rootCanvas
&& _rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay
&& _rootCanvas.worldCamera
&& _rootCanvas.worldCamera.allowDynamicResolution;
}
}
///
/// Called when the component is enabled.
///
protected override void OnEnable()
{
UIExtraCallbacks.onBeforeCanvasRebuild +=
_onBeforeCanvasRebuild ?? (_onBeforeCanvasRebuild = OnBeforeCanvasRebuild);
UIExtraCallbacks.onAfterCanvasRebuild +=
_renderSoftMaskBuffer ?? (_renderSoftMaskBuffer = RenderSoftMaskBuffer);
if (graphic)
{
graphic.RegisterDirtyMaterialCallback(
_updateParentSoftMask ?? (_updateParentSoftMask = UpdateParentSoftMask));
graphic.RegisterDirtyVerticesCallback(
_setSoftMaskDirty ?? (_setSoftMaskDirty = SetSoftMaskDirty));
graphic.SetVerticesDirty();
}
AddSoftMaskableOnChildren();
OnCanvasHierarchyChanged();
_shapeContainer = GetComponent();
base.OnEnable();
}
///
/// Called when the component is disabled.
///
protected override void OnDisable()
{
UIExtraCallbacks.onBeforeCanvasRebuild -=
_onBeforeCanvasRebuild ?? (_onBeforeCanvasRebuild = OnBeforeCanvasRebuild);
UIExtraCallbacks.onAfterCanvasRebuild -=
_renderSoftMaskBuffer ?? (_renderSoftMaskBuffer = RenderSoftMaskBuffer);
if (graphic)
{
graphic.UnregisterDirtyMaterialCallback(
_updateParentSoftMask ?? (_updateParentSoftMask = UpdateParentSoftMask));
graphic.UnregisterDirtyVerticesCallback(
_setSoftMaskDirty ?? (_setSoftMaskDirty = SetSoftMaskDirty));
graphic.SetVerticesDirty();
}
UpdateParentSoftMask(null);
children.Clear();
MeshExtensions.Return(ref _mesh);
SoftMaskUtils.materialPropertyBlockPool.Return(ref _mpb);
SoftMaskUtils.commandBufferPool.Return(ref _cb);
RenderTextureRepository.Release(ref _softMaskBuffer);
UpdateCanvasViewChangeTrigger(null);
_rootCanvas = null;
_shapeContainer = null;
UpdateAntiAlias();
base.OnDisable();
}
protected override void OnDestroy()
{
InternalListPool.Return(ref _children);
_onBeforeCanvasRebuild = null;
_renderSoftMaskBuffer = null;
_setSoftMaskDirty = null;
_onCanvasViewChanged = null;
_updateParentSoftMask = null;
}
///
/// Called when the state of the parent Canvas is changed.
///
protected override void OnCanvasHierarchyChanged()
{
if (!isActiveAndEnabled) return;
_rootCanvas = this.GetRootComponent