3.0.0-preview.28

# [3.0.0-preview.28](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.27...v3.0.0-preview.28) (2020-09-01)

### Features

* support AnimatableProperty for multiple materials ([062d988](062d9887fb))
pull/120/head
semantic-release-bot 2020-09-01 17:39:05 +00:00
parent 2b9e8ecd73
commit 7f36ca15dd
11 changed files with 352 additions and 175 deletions

View File

@ -1,3 +1,10 @@
# [3.0.0-preview.28](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.27...v3.0.0-preview.28) (2020-09-01)
### Features
* support AnimatableProperty for multiple materials ([062d988](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/062d9887fb8b096250ec3b43d9aa82637940a8bb))
# [3.0.0-preview.27](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.26...v3.0.0-preview.27) (2020-09-01)

View File

@ -45,7 +45,7 @@ namespace Coffee.UIExtensions
}
private Camera _camera;
private int _refCount;
// private int _refCount;
private static BakingCamera Create()
{
@ -70,26 +70,6 @@ namespace Coffee.UIExtensions
DontDestroyOnLoad(gameObject);
}
public static void Register()
{
Instance._refCount++;
}
public static void Unregister()
{
if (s_Instance == null) return;
Instance._refCount--;
if (0 < Instance._refCount) return;
if (Application.isPlaying)
Destroy(Instance.gameObject);
else
DestroyImmediate(Instance.gameObject);
s_Instance = null;
}
public static Camera GetCamera(Canvas canvas)
{
if (!canvas) return Camera.main;

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Coffee.UIExtensions
{
internal class CombineInstanceEx
{
private int count;
public long hash = -1;
public int index = -1;
private readonly List<CombineInstance> combineInstances = new List<CombineInstance>(32);
public Mesh mesh;
public Matrix4x4 transform;
public void Combine()
{
switch (count)
{
case 0:
return;
case 1:
mesh = combineInstances[0].mesh;
transform = combineInstances[0].transform;
return;
default:
{
var cis = CombineInstanceArrayPool.Get(combineInstances);
mesh = MeshPool.Rent();
mesh.CombineMeshes(cis, true, true);
transform = Matrix4x4.identity;
cis.Clear();
return;
}
}
}
public void Clear()
{
for (var i = 0; i < combineInstances.Count; i++)
{
var inst = combineInstances[i];
MeshPool.Return(inst.mesh);
inst.mesh = null;
combineInstances[i] = inst;
}
combineInstances.Clear();
MeshPool.Return(mesh);
mesh = null;
count = 0;
hash = -1;
index = -1;
}
public void Push(Mesh mesh, Matrix4x4 transform)
{
combineInstances.Add(new CombineInstance {mesh = mesh, transform = transform});
count++;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0328b9fb8360e4f8e8a842f87d330466
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,3 +1,4 @@
using System;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
@ -8,8 +9,9 @@ namespace Coffee.UIExtensions
{
internal class AnimatedPropertiesEditor
{
static readonly List<string> s_ActiveNames = new List<string>();
static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
private static readonly List<string> s_ActiveNames = new List<string>();
private static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
private static readonly HashSet<string> s_Names = new HashSet<string>();
private string _name;
private ShaderPropertyType _type;
@ -39,10 +41,8 @@ namespace Coffee.UIExtensions
return s_Sb.ToString();
}
public static void DrawAnimatableProperties(SerializedProperty sp, Material mat)
public static void DrawAnimatableProperties(SerializedProperty sp, Material[] mats)
{
if (!mat || !mat.shader) return;
bool isClicked;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false)))
{
@ -72,17 +72,27 @@ namespace Coffee.UIExtensions
}
}
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
s_Names.Clear();
foreach (var mat in mats)
{
var pName = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (ShaderPropertyType) ShaderUtil.GetPropertyType(mat.shader, i);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName, _type = type}, true);
if (!mat || !mat.shader) continue;
if (type != ShaderPropertyType.Texture) continue;
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
{
var pName = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (ShaderPropertyType) ShaderUtil.GetPropertyType(mat.shader, i);
var name = string.Format("{0} ({1})", pName, type);
if (s_Names.Contains(name)) continue;
s_Names.Add(name);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_ST", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_HDR", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_TexelSize", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName, _type = type}, true);
if (type != ShaderPropertyType.Texture) continue;
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_ST", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_HDR", _type = ShaderPropertyType.Vector}, true);
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_TexelSize", _type = ShaderPropertyType.Vector}, true);
}
}
gm.ShowAsContext();

View File

@ -2,6 +2,7 @@ using UnityEditor;
using UnityEditor.UI;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEditorInternal;
using UnityEngine.UI;
@ -100,7 +101,12 @@ namespace Coffee.UIExtensions
EditorGUILayout.PropertyField(_spScale);
// AnimatableProperties
AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, current.material);
var mats = current.particles
.Where(x => x)
.Select(x => x.GetComponent<ParticleSystemRenderer>().sharedMaterial)
.Where(x => x)
.ToArray();
AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, mats);
_ro.DoLayoutList();
@ -125,19 +131,21 @@ namespace Coffee.UIExtensions
// Does the shader support UI masks?
if (FixButton(current.m_IsTrail,"This UIParticle component should be removed. The UIParticle for trails is no longer needed."))
if (FixButton(current.m_IsTrail, "This UIParticle component should be removed. The UIParticle for trails is no longer needed."))
{
DestroyUIParticle(current);
return;
}
current.GetComponentsInParent(true, s_TempParents);
if (FixButton(1 < s_TempParents.Count,"This UIParticle component should be removed. The parent UIParticle exists."))
if (FixButton(1 < s_TempParents.Count, "This UIParticle component should be removed. The parent UIParticle exists."))
{
DestroyUIParticle(current);
return;
}
current.GetComponentsInChildren(true, s_TempChildren);
if (FixButton(1 < s_TempChildren.Count,"The children UIParticle component should be removed."))
if (FixButton(1 < s_TempChildren.Count, "The children UIParticle component should be removed."))
{
s_TempChildren.ForEach(child => DestroyUIParticle(child, true));
}

View File

@ -1,104 +1,106 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace Coffee.UIExtensions
{
internal static class MeshHelper
{
private static CombineInstance[] s_CombineInstances;
private static int s_TempIndex;
private static int s_CurrentIndex;
static readonly List<Color32> s_Colors = new List<Color32>();
private static int s_RefCount;
private static Matrix4x4 s_Transform;
public static uint activeMeshIndices { get; private set; }
public static long activeMeshIndices { get; private set; }
private static readonly List<CombineInstanceEx> s_CachedInstance;
private static int count;
public static void Register()
public static void Init()
{
if (0 < s_RefCount++) return;
s_CombineInstances = new CombineInstance[8];
}
public static void Unregister()
static MeshHelper()
{
s_RefCount--;
if (0 < s_RefCount || s_CombineInstances == null) return;
for (var i = 0; i < s_CombineInstances.Length; i++)
s_CachedInstance = new List<CombineInstanceEx>(8);
for (var i = 0; i < 8; i++)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Object.DestroyImmediate(s_CombineInstances[i].mesh);
else
#endif
{
Object.Destroy(s_CombineInstances[i].mesh);
}
s_CachedInstance.Add(new CombineInstanceEx());
}
}
private static CombineInstanceEx Get(int index, long hash)
{
if (0 < count && s_CachedInstance[count - 1].hash == hash)
return s_CachedInstance[count - 1];
if (s_CachedInstance.Count <= count)
{
var newInst = new CombineInstanceEx();
s_CachedInstance.Add(newInst);
}
s_CombineInstances = null;
var inst = s_CachedInstance[count];
inst.hash = hash;
if (inst.index != -1) return inst;
inst.index = index;
count++;
return inst;
}
public static Mesh GetTemporaryMesh(int index)
public static Mesh GetTemporaryMesh()
{
if (s_CombineInstances.Length <= s_TempIndex) s_TempIndex = s_CombineInstances.Length - 1;
s_CurrentIndex = index;
activeMeshIndices += (uint)(1 << s_CurrentIndex);
s_CombineInstances[s_TempIndex].transform = s_Transform;
return s_CombineInstances[s_TempIndex++].mesh;
return MeshPool.Rent();
}
public static void DiscardTemporaryMesh()
public static void Push(int index, long hash, Mesh mesh, Matrix4x4 transform)
{
if (s_TempIndex == 0) return;
s_TempIndex--;
activeMeshIndices -= (uint)(1 << s_CurrentIndex);
}
if (mesh.vertexCount <= 0)
{
DiscardTemporaryMesh(mesh);
return;
}
public static void SetTransform(Matrix4x4 transform)
{
s_Transform = transform;
Profiler.BeginSample("[UIParticle] MeshHelper > Get CombineInstanceEx");
var inst = Get(index, hash);
Profiler.EndSample();
Profiler.BeginSample("[UIParticle] MeshHelper > Push To Mesh Helper");
inst.Push(mesh, transform);
Profiler.EndSample();
activeMeshIndices |= (long) 1 << inst.index;
}
public static void Clear()
{
if (s_CombineInstances == null) return;
s_CurrentIndex = 0;
count = 0;
activeMeshIndices = 0;
s_TempIndex = 0;
for (var i = 0; i < s_CombineInstances.Length; i++)
foreach (var inst in s_CachedInstance)
{
if (!s_CombineInstances[i].mesh)
{
var mesh = new Mesh();
mesh.MarkDynamic();
s_CombineInstances[i].mesh = mesh;
}
else
{
s_CombineInstances[i].mesh.Clear(false);
}
inst.Clear();
}
}
public static void CombineMesh(Mesh result)
{
if (!result || s_TempIndex == 0) return;
if (count == 0) return;
for (var i = 0; i < count; i++)
{
Profiler.BeginSample("[UIParticle] MeshHelper > Combine Mesh Internal");
s_CachedInstance[i].Combine();
Profiler.EndSample();
}
Profiler.BeginSample("[UIParticle] MeshHelper > Combine Mesh");
var cis = CombineInstanceArrayPool.Get(s_CachedInstance, count);
result.CombineMeshes(cis, false, true);
cis.Clear();
Profiler.EndSample();
result.CombineMeshes(s_CombineInstances, false, true);
result.RecalculateBounds();
}
public static void ModifyColorSpaceToLinear(this Mesh self)
public static void DiscardTemporaryMesh(Mesh mesh)
{
self.GetColors(s_Colors);
for (var i = 0; i < s_Colors.Count; i++)
s_Colors[i] = ((Color) s_Colors[i]).gamma;
self.SetColors(s_Colors);
s_Colors.Clear();
MeshPool.Return(mesh);
}
}
}

View File

@ -39,7 +39,7 @@ namespace Coffee.UIExtensions
private Mesh _bakedMesh;
private readonly List<Material> _modifiedMaterials = new List<Material>();
private readonly List<Material> _maskMaterials = new List<Material>();
private uint _activeMeshIndices;
private long _activeMeshIndices;
private Vector3 _cachedPosition;
private static readonly List<Material> s_TempMaterials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb;
@ -91,7 +91,7 @@ namespace Coffee.UIExtensions
get { return _modifiedMaterials; }
}
internal uint activeMeshIndices
internal long activeMeshIndices
{
get { return _activeMeshIndices; }
set
@ -190,7 +190,7 @@ namespace Coffee.UIExtensions
r.GetSharedMaterials(s_TempMaterials);
// Main
var bit = 1 << (i * 2);
var bit = (long) 1 << (i * 2);
if (0 < (activeMeshIndices & bit) && 0 < s_TempMaterials.Count)
{
var mat = GetModifiedMaterial(s_TempMaterials[0], ps.GetTextureForSprite());
@ -264,8 +264,7 @@ namespace Coffee.UIExtensions
}
// Create objects.
_bakedMesh = new Mesh();
_bakedMesh.MarkDynamic();
_bakedMesh = MeshPool.Rent();
base.OnEnable();
@ -283,7 +282,7 @@ namespace Coffee.UIExtensions
_tracker.Clear();
// Destroy object.
DestroyImmediate(_bakedMesh);
MeshPool.Return(_bakedMesh);
_bakedMesh = null;
base.OnDisable();

View File

@ -16,18 +16,12 @@ namespace Coffee.UIExtensions
{
if (!particle) return;
s_ActiveParticles.Add(particle);
MeshHelper.Register();
BakingCamera.Register();
}
public static void Unregister(UIParticle particle)
{
if (!particle) return;
s_ActiveParticles.Remove(particle);
MeshHelper.Unregister();
BakingCamera.Unregister();
}
#if UNITY_EDITOR
@ -36,6 +30,10 @@ namespace Coffee.UIExtensions
[RuntimeInitializeOnLoadMethod]
private static void InitializeOnLoad()
{
MeshHelper.Init();
MeshPool.Init();
CombineInstanceArrayPool.Init();
Canvas.willRenderCanvases -= Refresh;
Canvas.willRenderCanvases += Refresh;
}
@ -59,26 +57,26 @@ namespace Coffee.UIExtensions
{
if (!particle || !particle.canvas || !particle.canvasRenderer) return;
Profiler.BeginSample("Modify scale");
Profiler.BeginSample("[UIParticle] Modify scale");
ModifyScale(particle);
Profiler.EndSample();
Profiler.BeginSample("Bake mesh");
Profiler.BeginSample("[UIParticle] Bake mesh");
BakeMesh(particle);
Profiler.EndSample();
if (QualitySettings.activeColorSpace == ColorSpace.Linear)
{
Profiler.BeginSample("Modify color space to linear");
Profiler.BeginSample("[UIParticle] Modify color space to linear");
particle.bakedMesh.ModifyColorSpaceToLinear();
Profiler.EndSample();
}
Profiler.BeginSample("Set mesh to CanvasRenderer");
Profiler.BeginSample("[UIParticle] Set mesh to CanvasRenderer");
particle.canvasRenderer.SetMesh(particle.bakedMesh);
Profiler.EndSample();
Profiler.BeginSample("Update Animatable Material Properties");
Profiler.BeginSample("[UIParticle] Update Animatable Material Properties");
// UpdateAnimatableMaterialProperties(particle);
Profiler.EndSample();
}
@ -170,12 +168,12 @@ namespace Coffee.UIExtensions
matrix = GetScaledMatrix(currentPs);
}
// Set transform
MeshHelper.SetTransform(scaleMatrix * matrix);
matrix = scaleMatrix * matrix;
// Extra world simulation.
if (currentPs.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < diff.sqrMagnitude)
{
Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation");
var count = currentPs.particleCount;
if (s_Particles.Length < count)
{
@ -192,48 +190,55 @@ namespace Coffee.UIExtensions
}
currentPs.SetParticles(s_Particles, count);
Profiler.EndSample();
}
// Bake main particles.
var r = currentPs.GetComponent<ParticleSystemRenderer>();
if (CanBakeMesh(r))
{
var m = MeshHelper.GetTemporaryMesh(i * 2);
r.BakeMesh(m, camera, true);
if (m.vertexCount == 0)
MeshHelper.DiscardTemporaryMesh();
else
Profiler.BeginSample("[UIParticle] Bake Mesh > Bake Main Particles");
var hash = currentPs.GetMaterialHash(false);
if (hash != 0)
{
var index = MeshHelper.activeMeshIndices.BitCount() - 1;
particle.UpdateMaterialProperties(r, index);
var m = MeshHelper.GetTemporaryMesh();
r.BakeMesh(m, camera, true);
MeshHelper.Push(i * 2, hash, m, matrix);
}
Profiler.EndSample();
}
// Bake trails particles.
if (currentPs.trails.enabled)
{
var m = MeshHelper.GetTemporaryMesh(i * 2 + 1);
try
Profiler.BeginSample("[UIParticle] Bake Mesh > Bake Trails Particles");
var hash = currentPs.GetMaterialHash(true);
if (hash != 0)
{
r.BakeTrailsMesh(m, camera, true);
var m = MeshHelper.GetTemporaryMesh();
try
{
r.BakeTrailsMesh(m, camera, true);
MeshHelper.Push(i * 2 + 1, hash, m, matrix);
}
catch
{
MeshHelper.DiscardTemporaryMesh(m);
}
}
if (m.vertexCount == 0)
MeshHelper.DiscardTemporaryMesh();
}
catch
{
MeshHelper.DiscardTemporaryMesh();
}
Profiler.EndSample();
}
}
// Set active indices.
particle.activeMeshIndices = MeshHelper.activeMeshIndices;
// Combine
Profiler.BeginSample("[UIParticle] Bake Mesh > CombineMesh");
MeshHelper.CombineMesh(particle.bakedMesh);
Profiler.EndSample();
}
private static bool CanBakeMesh(ParticleSystemRenderer renderer)
@ -246,31 +251,5 @@ namespace Coffee.UIExtensions
return true;
}
/// <summary>
/// Copy the value from MaterialPropertyBlock to CanvasRenderer
/// </summary>
private static void UpdateAnimatableMaterialProperties(UIParticle particle, ParticleSystemRenderer renderer)
{
#if UNITY_EDITOR
if (!Application.isPlaying) return;
#endif
if (0 == particle.m_AnimatableProperties.Length) return;
if (0 == particle.canvasRenderer.materialCount) return;
var mat = particle.canvasRenderer.GetMaterial(0);
if (!mat) return;
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
if (s_Mpb == null)
s_Mpb = new MaterialPropertyBlock();
renderer.GetPropertyBlock(s_Mpb);
foreach (var ap in particle.m_AnimatableProperties)
{
ap.UpdateMaterialProperties(mat, s_Mpb);
}
s_Mpb.Clear();
}
}
}

View File

@ -30,15 +30,120 @@ namespace Coffee.UIExtensions
#endif
}
internal static class UintExtensions
internal static class LongExtensions
{
public static int BitCount(this uint self)
public static int BitCount(this long self)
{
self = (self & 0x55555555) + ((self >> 1) & 0x55555555);
self = (self & 0x33333333) + ((self >> 2) & 0x33333333);
self = (self & 0x0F0F0F0F) + ((self >> 4) & 0x0F0F0F0F);
self = (self & 0x00FF00FF) + ((self >> 8) & 0x00FF00FF);
return (int) ((self & 0x0000ffff) + (self >> 16));
self = self - ((self >> 1) & 0x5555555555555555L);
self = (self & 0x3333333333333333L) + ((self >> 2) & 0x3333333333333333L);
return (int) (unchecked(((self + (self >> 4)) & 0xF0F0F0F0F0F0F0FL) * 0x101010101010101L) >> 56);
}
}
internal static class MeshExtensions
{
static readonly List<Color32> s_Colors = new List<Color32>();
public static void ModifyColorSpaceToLinear(this Mesh self)
{
self.GetColors(s_Colors);
for (var i = 0; i < s_Colors.Count; i++)
s_Colors[i] = ((Color) s_Colors[i]).gamma;
self.SetColors(s_Colors);
s_Colors.Clear();
}
public static void Clear(this CombineInstance[] self)
{
for (var i = 0; i < self.Length; i++)
{
MeshPool.Return(self[i].mesh);
self[i].mesh = null;
}
}
}
internal static class MeshPool
{
private static readonly Stack<Mesh> s_Pool = new Stack<Mesh>();
public static void Init()
{
}
static MeshPool()
{
for (var i = 0; i < 32; i++)
{
var m = new Mesh();
m.MarkDynamic();
s_Pool.Push(m);
}
}
public static Mesh Rent()
{
Mesh m;
while (0 < s_Pool.Count)
{
m = s_Pool.Pop();
if (m) return m;
}
m = new Mesh();
m.MarkDynamic();
return m;
}
public static void Return(Mesh mesh)
{
if (!mesh || s_Pool.Contains(mesh)) return;
mesh.Clear(false);
s_Pool.Push(mesh);
}
}
internal static class CombineInstanceArrayPool
{
private static readonly List<CombineInstance[]> s_Pool;
public static void Init()
{
}
static CombineInstanceArrayPool()
{
s_Pool = new List<CombineInstance[]>(32);
for (var i = 0; i < 32; i++)
{
s_Pool.Add(new CombineInstance[i]);
}
}
public static CombineInstance[] Get(List<CombineInstance> src)
{
var dst = s_Pool[src.Count];
for (var i = 0; i < src.Count; i++)
{
dst[i].mesh = src[i].mesh;
dst[i].transform = src[i].transform;
}
return dst;
}
public static CombineInstance[] Get(List<CombineInstanceEx> src, int count)
{
var dst = s_Pool[count];
for (var i = 0; i < count; i++)
{
dst[i].mesh = src[i].mesh;
dst[i].transform = src[i].transform;
}
return dst;
}
}
@ -66,6 +171,19 @@ namespace Coffee.UIExtensions
});
}
public static long GetMaterialHash(this ParticleSystem self, bool trail)
{
if (!self) return 0;
var r = self.GetComponent<ParticleSystemRenderer>();
var mat = trail ? r.trailMaterial : r.sharedMaterial;
if (!mat) return 0;
var tex = self.GetTextureForSprite();
return ((long) mat.GetHashCode() << 32) + (tex ? tex.GetHashCode() : 0);
}
public static Texture2D GetTextureForSprite(this ParticleSystem self)
{
if (!self) return null;
@ -91,5 +209,4 @@ namespace Coffee.UIExtensions
self.ForEach(action);
}
}
}

View File

@ -2,7 +2,7 @@
"name": "com.coffee.ui-particle",
"displayName": "UI Particle",
"description": "This plugin provide a component to render particle effect for uGUI.\nThe particle rendering is maskable and sortable, without Camera, RenderTexture or Canvas.",
"version": "3.0.0-preview.27",
"version": "3.0.0-preview.28",
"unity": "2018.2",
"license": "MIT",
"repository": {