using System; using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Mathematics; using Unity.Profiling; using UnityEngine.U2D.Common; namespace UnityEngine.U2D.Animation { internal abstract class BaseDeformationSystem { protected static class Profiling { public static readonly ProfilerMarker transformAccessJob = new ProfilerMarker("BaseDeformationSystem.TransformAccessJob"); public static readonly ProfilerMarker boneTransformsChangeDetection = new ProfilerMarker("BaseDeformationSystem.BoneTransformsChangeDetection"); public static readonly ProfilerMarker getSpriteSkinBatchData = new ProfilerMarker("BaseDeformationSystem.GetSpriteSkinBatchData"); public static readonly ProfilerMarker scheduleJobs = new ProfilerMarker("BaseDeformationSystem.ScheduleJobs"); public static readonly ProfilerMarker setBatchDeformableBufferAndLocalAABB = new ProfilerMarker("BaseDeformationSystem.SetBatchDeformableBufferAndLocalAABB"); public static readonly ProfilerMarker setBoneTransformsArray = new ProfilerMarker("BaseDeformationSystem.SetBoneTransformsArray"); } public abstract DeformationMethods deformationMethod { get; } protected int m_ObjectId; protected readonly HashSet m_SpriteSkins = new HashSet(); #if UNITY_INCLUDE_TESTS internal HashSet SpriteSkins => m_SpriteSkins; #endif protected SpriteRenderer[] m_SpriteRenderers = new SpriteRenderer[0]; // This is a queue of sprite skins which will be added into m_SpriteSkins // at the correct time in batch processing BatchAddSpriteSkins readonly HashSet m_SpriteSkinsToAdd = new HashSet(); // This is a queue of sprite skins which will be removed from m_SpriteSkins // at the correct time in batch processing BatchRemoveSpriteSkins readonly HashSet m_SpriteSkinsToRemove = new HashSet(); readonly List m_TransformIdsToRemove = new List(); protected NativeByteArray m_DeformedVerticesBuffer; protected NativeByteArray m_PreviousDeformedVerticesBuffer; protected NativeArray m_FinalBoneTransforms; protected NativeArray m_IsSpriteSkinActiveForDeform; protected NativeArray m_SpriteSkinData; protected NativeArray m_PerSkinJobData; protected NativeArray m_BoundsData; protected NativeArray m_Buffers; protected NativeArray m_BufferSizes; protected NativeArray m_BoneTransformBuffers; protected NativeArray m_BoneLookupData; protected NativeArray m_SkinBatchArray; // Indicates whether bone transforms have changed for each SpriteSkin in the current frame. // Set to true if any bone transform changes require the deformation job to run. protected NativeArray m_HasBoneTransformsChanged; // The last frame when deformation occurred for each SpriteSkin. // Used to determine if cached deformation data is still valid or needs to be updated. protected NativeArray m_LastDeformedFrame; protected TransformAccessJob m_LocalToWorldTransformAccessJob; protected TransformAccessJob m_WorldToLocalTransformAccessJob; protected JobHandle m_DeformJobHandle; internal void RemoveBoneTransforms(SpriteSkin spriteSkin) { // if the sprite skin is not in the list, we don't need to remove it if (!m_SpriteSkins.Contains(spriteSkin)) return; m_LocalToWorldTransformAccessJob.RemoveTransformById(spriteSkin.rootBoneTransformId); NativeArray boneTransforms = spriteSkin.boneTransformId; if (boneTransforms == default || !boneTransforms.IsCreated) return; for (int i = 0; i < boneTransforms.Length; ++i) m_LocalToWorldTransformAccessJob.RemoveTransformById(boneTransforms[i]); } internal void AddBoneTransforms(SpriteSkin spriteSkin) { // if we are not handling this spriteskin, we don't need to add it to the job. if (!m_SpriteSkins.Contains(spriteSkin)) return; m_LocalToWorldTransformAccessJob.AddTransform(spriteSkin.rootBone); if (spriteSkin.boneTransforms != null) { foreach (Transform t in spriteSkin.boneTransforms) { if (t != null) m_LocalToWorldTransformAccessJob.AddTransform(t); } } } internal virtual void UpdateMaterial(SpriteSkin spriteSkin) { } // This is called when the SpriteSkin is created or enabled internal virtual bool AddSpriteSkin(SpriteSkin spriteSkin) { // if we do not have the sprite skin and it is already in the m_SpriteSkinsToAdd list, we don't need to add it again if (!m_SpriteSkins.Contains(spriteSkin) && m_SpriteSkinsToAdd.Add(spriteSkin)) { return true; } // if the skin is scheduled to be removed, cancel that. if (!m_SpriteSkinsToRemove.Contains(spriteSkin)) return false; m_SpriteSkinsToAdd.Add(spriteSkin); return true; } internal void CopyToSpriteSkinData(SpriteSkin spriteSkin) { if (!m_SpriteSkinData.IsCreated) throw new InvalidOperationException("Sprite Skin Data not initialized."); int dataIndex = spriteSkin.dataIndex; if (dataIndex < 0 || dataIndex >= m_SpriteSkinData.Length) return; SpriteSkinData spriteSkinData = default(SpriteSkinData); spriteSkin.CopyToSpriteSkinData(ref spriteSkinData); m_SpriteSkinData[dataIndex] = spriteSkinData; m_SpriteRenderers[dataIndex] = spriteSkin.spriteRenderer; } /// This is called when the SpriteSkin is destroyed or disabled internal void RemoveSpriteSkin(SpriteSkin spriteSkin) { if (spriteSkin == null) return; // if we are currently handling the spritekin and we have not yet removed it, mark it as being removed // by adding it to the m_SpriteSkinsToRemove list if (m_SpriteSkins.Contains(spriteSkin) && m_SpriteSkinsToRemove.Add(spriteSkin)) { // records the transform id to remove m_TransformIdsToRemove.Add(spriteSkin.transform.GetInstanceID()); } // if is scheduled for removal, also remove it from the m_SpriteSkinsToAdd list m_SpriteSkinsToAdd.Remove(spriteSkin); // remove bone transforms from the transform access job RemoveBoneTransforms(spriteSkin); } internal HashSet GetSpriteSkins() { return m_SpriteSkins; } internal void Initialize(int objectId) { m_ObjectId = objectId; // These two jobs have the same type, but can be configured to return localToWorld or worldToLocal matrices if (m_LocalToWorldTransformAccessJob == null) m_LocalToWorldTransformAccessJob = new TransformAccessJob(); if (m_WorldToLocalTransformAccessJob == null) m_WorldToLocalTransformAccessJob = new TransformAccessJob(); InitializeArrays(); BatchRemoveSpriteSkins(); BatchAddSpriteSkins(); // Initialise all existing SpriteSkins as execution order is indeterminate int count = 0; foreach (SpriteSkin spriteSkin in m_SpriteSkins) { spriteSkin.SetDataIndex(count++); CopyToSpriteSkinData(spriteSkin); } } protected virtual void InitializeArrays() { const int startingCount = 0; m_FinalBoneTransforms = new NativeArray(startingCount, Allocator.Persistent); m_BoneLookupData = new NativeArray(startingCount, Allocator.Persistent); m_SkinBatchArray = new NativeArray(startingCount, Allocator.Persistent); m_IsSpriteSkinActiveForDeform = new NativeArray(startingCount, Allocator.Persistent); m_PerSkinJobData = new NativeArray(startingCount, Allocator.Persistent); m_SpriteSkinData = new NativeArray(startingCount, Allocator.Persistent); m_BoundsData = new NativeArray(startingCount, Allocator.Persistent); m_Buffers = new NativeArray(startingCount, Allocator.Persistent); m_BufferSizes = new NativeArray(startingCount, Allocator.Persistent); m_HasBoneTransformsChanged = new NativeArray(startingCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); m_LastDeformedFrame = new NativeArray(startingCount, Allocator.Persistent); } protected void BatchRemoveSpriteSkins() { m_WorldToLocalTransformAccessJob.RemoveTransformsIfNull(); int spritesToRemoveCount = m_SpriteSkinsToRemove.Count; if (spritesToRemoveCount == 0) return; m_WorldToLocalTransformAccessJob.RemoveTransformsByIds(m_TransformIdsToRemove); int updatedCount = Math.Max(m_SpriteSkins.Count - spritesToRemoveCount, 0); if (updatedCount == 0) { m_SpriteSkins.Clear(); } else { foreach (SpriteSkin spriteSkin in m_SpriteSkinsToRemove) m_SpriteSkins.Remove(spriteSkin); } int count = 0; foreach (SpriteSkin spriteSkin in m_SpriteSkins) { spriteSkin.SetDataIndex(count++); CopyToSpriteSkinData(spriteSkin); } Array.Resize(ref m_SpriteRenderers, updatedCount); ResizeAndCopyArrays(updatedCount); m_TransformIdsToRemove.Clear(); m_SpriteSkinsToRemove.Clear(); } protected void BatchAddSpriteSkins() { if (m_SpriteSkinsToAdd.Count == 0) return; if (!m_IsSpriteSkinActiveForDeform.IsCreated) throw new InvalidOperationException("SpriteSkinActiveForDeform not initialized."); int updatedCount = m_SpriteSkins.Count + m_SpriteSkinsToAdd.Count; Array.Resize(ref m_SpriteRenderers, updatedCount); ResizeAndCopyArrays(updatedCount); foreach (SpriteSkin spriteSkin in m_SpriteSkinsToAdd) { if (!m_SpriteSkins.Add(spriteSkin)) { Debug.LogError($"Skin already exists! Name={spriteSkin.name}"); continue; } UpdateMaterial(spriteSkin); int count = m_SpriteSkins.Count; m_SpriteRenderers[count - 1] = spriteSkin.spriteRenderer; m_WorldToLocalTransformAccessJob.AddTransform(spriteSkin.transform); AddBoneTransforms(spriteSkin); spriteSkin.SetDataIndex(count - 1); CopyToSpriteSkinData(spriteSkin); } m_SpriteSkinsToAdd.Clear(); } protected virtual void ResizeAndCopyArrays(int updatedCount) { NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_IsSpriteSkinActiveForDeform, updatedCount); NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_PerSkinJobData, updatedCount); NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_Buffers, updatedCount); NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BufferSizes, updatedCount); NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_SpriteSkinData, updatedCount); // Bounds may be reusable if index is unchanged. NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_BoundsData, updatedCount); // No need to copy or initialize values as they are completely overwritten each frame by the job NativeArrayHelpers.ResizeIfNeeded(ref m_HasBoneTransformsChanged, updatedCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); // Must be initialized to 0, because 0 means "not deformed yet" NativeArrayHelpers.ResizeAndCopyIfNeeded(ref m_LastDeformedFrame, updatedCount); } protected virtual void ResizeBuffers(int vertexBufferSize, in PerSkinJobData skinBatch) { if (m_DeformedVerticesBuffer != null) m_PreviousDeformedVerticesBuffer = m_DeformedVerticesBuffer; else m_PreviousDeformedVerticesBuffer = BufferManager.instance.GetBuffer(m_ObjectId, vertexBufferSize); m_DeformedVerticesBuffer = BufferManager.instance.GetBuffer(m_ObjectId, vertexBufferSize); NativeArrayHelpers.ResizeIfNeeded(ref m_FinalBoneTransforms, skinBatch.bindPosesIndex.y); NativeArrayHelpers.ResizeIfNeeded(ref m_BoneLookupData, skinBatch.bindPosesIndex.y); } internal virtual void Cleanup() { m_DeformJobHandle.Complete(); m_SpriteSkins.Clear(); m_SpriteRenderers = new SpriteRenderer[0]; BufferManager.instance.ReturnBuffer(m_ObjectId); m_IsSpriteSkinActiveForDeform.DisposeIfCreated(); m_PerSkinJobData.DisposeIfCreated(); m_Buffers.DisposeIfCreated(); m_BufferSizes.DisposeIfCreated(); m_SpriteSkinData.DisposeIfCreated(); m_BoneLookupData.DisposeIfCreated(); m_SkinBatchArray.DisposeIfCreated(); m_FinalBoneTransforms.DisposeIfCreated(); m_BoundsData.DisposeIfCreated(); m_HasBoneTransformsChanged.DisposeIfCreated(); m_LastDeformedFrame.DisposeIfCreated(); m_LocalToWorldTransformAccessJob.Destroy(); m_WorldToLocalTransformAccessJob.Destroy(); } internal abstract void Update(); protected void PrepareDataForDeformation(out JobHandle localToWorldJobHandle, out JobHandle worldToLocalJobHandle) { ValidateSpriteSkinData(); using (Profiling.transformAccessJob.Auto()) { localToWorldJobHandle = m_LocalToWorldTransformAccessJob.StartLocalToWorldAndChangeDetectionJob(); worldToLocalJobHandle = m_WorldToLocalTransformAccessJob.StartWorldToLocalJob(); } using (Profiling.boneTransformsChangeDetection.Auto()) { BoneTransformsChangeDetectionJob boneTransformChangeDetectionJob = new BoneTransformsChangeDetectionJob { transformChanged = m_LocalToWorldTransformAccessJob.transformChanged, boneTransformIndex = m_LocalToWorldTransformAccessJob.transformData, spriteSkinData = m_SpriteSkinData, hasBoneTransformsChanged = m_HasBoneTransformsChanged }; // Use 64 as the batch size to avoid false sharing boneTransformChangeDetectionJob.Schedule(m_SpriteSkinData.Length, 64, localToWorldJobHandle).Complete(); } using (Profiling.getSpriteSkinBatchData.Auto()) { NativeArrayHelpers.ResizeIfNeeded(ref m_SkinBatchArray, 1); FillPerSkinJobSingleThread fillPerSkinJobSingleThread = new FillPerSkinJobSingleThread() { isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform, combinedSkinBatchArray = m_SkinBatchArray, spriteSkinDataArray = m_SpriteSkinData, perSkinJobDataArray = m_PerSkinJobData, }; fillPerSkinJobSingleThread.Run(); } } void ValidateSpriteSkinData() { foreach (SpriteSkin spriteSkin in m_SpriteSkins) { int index = spriteSkin.dataIndex; m_IsSpriteSkinActiveForDeform[index] = spriteSkin.BatchValidate(); if (m_IsSpriteSkinActiveForDeform[index] && spriteSkin.NeedToUpdateDeformationCache()) CopyToSpriteSkinData(spriteSkin); } } protected bool GotVerticesToDeform(out int vertexBufferSize) { vertexBufferSize = m_SkinBatchArray[0].deformVerticesStartPos; return vertexBufferSize > 0; } protected JobHandle SchedulePrepareJob(int batchCount) { PrepareDeformJob prepareJob = new PrepareDeformJob { batchDataSize = batchCount, perSkinJobData = m_PerSkinJobData, boneLookupData = m_BoneLookupData }; return prepareJob.Schedule(); } protected JobHandle ScheduleBoneJobBatched(JobHandle jobHandle, PerSkinJobData skinBatch) { BoneDeformBatchedJob boneJobBatched = new BoneDeformBatchedJob() { boneTransform = m_LocalToWorldTransformAccessJob.transformMatrix, rootTransform = m_WorldToLocalTransformAccessJob.transformMatrix, spriteSkinData = m_SpriteSkinData, boneLookupData = m_BoneLookupData, finalBoneTransforms = m_FinalBoneTransforms, rootTransformIndex = m_WorldToLocalTransformAccessJob.transformData, boneTransformIndex = m_LocalToWorldTransformAccessJob.transformData }; jobHandle = boneJobBatched.Schedule(skinBatch.bindPosesIndex.y, 8, jobHandle); return jobHandle; } protected JobHandle ScheduleSkinDeformBatchedJob(JobHandle jobHandle, PerSkinJobData skinBatch, int spriteCount, int frameCount) { SkinDeformBatchedJob skinJobBatched = new SkinDeformBatchedJob { spriteSkinData = m_SpriteSkinData, perSkinJobData = m_PerSkinJobData, finalBoneTransforms = m_FinalBoneTransforms, vertices = m_DeformedVerticesBuffer.array, previousVertices = m_PreviousDeformedVerticesBuffer.array, isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform, hasBoneTransformsChanged = m_HasBoneTransformsChanged, bounds = m_BoundsData, lastDeformedFrame = m_LastDeformedFrame, frameCount = frameCount }; return skinJobBatched.Schedule(spriteCount, 1, jobHandle); } protected unsafe JobHandle ScheduleCopySpriteRendererBuffersJob(JobHandle jobHandle, int batchCount) { CopySpriteRendererBuffersJob copySpriteRendererBuffersJob = new CopySpriteRendererBuffersJob() { isSpriteSkinValidForDeformArray = m_IsSpriteSkinActiveForDeform, spriteSkinData = m_SpriteSkinData, ptrVertices = (IntPtr)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(m_DeformedVerticesBuffer.array), buffers = m_Buffers, bufferSizes = m_BufferSizes, }; return copySpriteRendererBuffersJob.Schedule(batchCount, 16, jobHandle); } protected void DeactivateDeformableBuffers() { for (int i = 0; i < m_IsSpriteSkinActiveForDeform.Length; ++i) { if (m_IsSpriteSkinActiveForDeform[i] || InternalEngineBridge.IsUsingDeformableBuffer(m_SpriteRenderers[i], IntPtr.Zero)) continue; m_SpriteRenderers[i].DeactivateDeformableBuffer(); } } internal bool IsSpriteSkinActiveForDeformation(SpriteSkin spriteSkin) { return m_IsSpriteSkinActiveForDeform[spriteSkin.dataIndex]; } internal int GetLastDeformedFrame(SpriteSkin spriteSkin) { return m_LastDeformedFrame[spriteSkin.dataIndex]; } internal unsafe NativeArray GetDeformableBufferForSpriteSkin(SpriteSkin spriteSkin) { if (!m_SpriteSkins.Contains(spriteSkin)) return default; if (!m_DeformJobHandle.IsCompleted) m_DeformJobHandle.Complete(); SpriteSkinData skinData = m_SpriteSkinData[spriteSkin.dataIndex]; if (skinData.deformVerticesStartPos < 0) return default; int vertexBufferLength = skinData.spriteVertexCount * skinData.spriteVertexStreamSize; byte* ptrVertices = (byte*)m_DeformedVerticesBuffer.array.GetUnsafeReadOnlyPtr(); ptrVertices += skinData.deformVerticesStartPos; NativeArray buffer = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptrVertices, vertexBufferLength, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref buffer, NativeArrayUnsafeUtility.GetAtomicSafetyHandle(m_DeformedVerticesBuffer.array)); #endif return buffer; } #if UNITY_INCLUDE_TESTS internal TransformAccessJob GetWorldToLocalTransformAccessJob() => m_WorldToLocalTransformAccessJob; internal TransformAccessJob GetLocalToWorldTransformAccessJob() => m_LocalToWorldTransformAccessJob; #endif } }