using Rukhanka.Toolbox; using Unity.Burst; using Unity.Burst.Intrinsics; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; using Unity.Mathematics; using Unity.Rendering; using Unity.Transforms; using Hash128 = Unity.Entities.Hash128; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka { public partial class GPUAnimationSystem { [BurstCompile] struct CopyNewAnimationsToGPUJob: IJobFor { [ReadOnly] public NativeList newAnimationClips; public ThreadedSparseUploader gpuAnimationClips; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute(int index) { var animationClip = newAnimationClips[index]; ref var ac = ref animationClip.animationClipBlob.Value; var gpuAnimClipData = new GPUStructures.AnimationClip() { flags = ac.flags, hash = ac.hash.Value, length = ac.length, looped = ac.looped, cycleOffset = ac.cycleOffset, }; SetPerfectHashTableData(ref animationClip.animationClipBlob.Value.clipTracks, animationClip.clipTracks, ref gpuAnimClipData.clipTracks); SetPerfectHashTableData(ref animationClip.animationClipBlob.Value.additiveReferencePoseFrame, animationClip.additiveRefPoseTracks, ref gpuAnimClipData.additiveReferencePoseTracks); var absOffset = animationClip.animationClipDataOffset; gpuAnimationClips.AddUpload(gpuAnimClipData, absOffset); UploadTrackSet(absOffset, animationClip.clipTracks, ref animationClip.animationClipBlob.Value.clipTracks); UploadTrackSet(absOffset, animationClip.additiveRefPoseTracks, ref animationClip.animationClipBlob.Value.additiveReferencePoseFrame); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SetPerfectHashTableData(ref TrackSet ts, in GPUTrackSetPlacementData tspd, ref GPUStructures.TrackSet gpuTrackSet) { gpuTrackSet = tspd.ToGPUTrackSet(); gpuTrackSet.trackGroupPHTSeed = ts.trackGroupPHT.seed; gpuTrackSet.trackGroupPHTSizeMask = (uint)ts.trackGroupPHT.pht.Length - 1; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe void UploadTrackSet(int baseOffset, in GPUTrackSetPlacementData tspd, ref TrackSet ts) { if (ts.keyframes.Length == 0) return; gpuAnimationClips.AddUpload(ts.keyframes.GetUnsafePtr(), ts.keyframes.Length * UnsafeUtility.SizeOf(), baseOffset + tspd.keyFramesOffset); gpuAnimationClips.AddUpload(ts.trackGroups.GetUnsafePtr(), ts.trackGroups.Length * UnsafeUtility.SizeOf(), baseOffset + tspd.trackGroupsOffset); gpuAnimationClips.AddUpload(ts.trackGroupPHT.pht.GetUnsafePtr(), ts.trackGroupPHT.pht.Length * UnsafeUtility.SizeOf(), baseOffset + tspd.hashTableOffset); #if RUKHANKA_DEBUG_INFO // Must copy because debug tracks contain name field var tmpArr = new NativeArray(ts.tracks.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); for (var i = 0; i < tmpArr.Length; ++i) { var it = ts.tracks[i]; var t = new GPUStructures.Track() { props = it.props, keyFrameRange = it.keyFrameRange }; tmpArr[i] = t; } gpuAnimationClips.AddUpload(tmpArr, baseOffset + tspd.tracksOffset); #else gpuAnimationClips.AddUpload(ts.tracks.GetUnsafePtr(), ts.tracks.Length * UnsafeUtility.SizeOf(), baseOffset + tspd.tracksOffset); #endif } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct CopyNewAvatarMasksToGPUJob: IJobFor { [ReadOnly] public NativeList newAvatarMasks; public ThreadedSparseUploader gpuAvatarMasks; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public unsafe void Execute(int index) { var am = newAvatarMasks[index]; var dstOffset = am.dataOffset * UnsafeUtility.SizeOf(); gpuAvatarMasks.AddUpload(am.avatarMaskBlob.Value.humanBodyPartsAvatarMask, dstOffset); ref var bm = ref am.avatarMaskBlob.Value.includedBoneMask; var srcPtr = bm.GetUnsafePtr(); var srcSize = bm.Length * UnsafeUtility.SizeOf(); gpuAvatarMasks.AddUpload(srcPtr, srcSize, dstOffset + 4); } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct CopyNewRigsToGPUJob: IJobFor { [ReadOnly] public NativeList newRigs; public ThreadedSparseUploader gpuRigDefs; public ThreadedSparseUploader gpuRigBones; public ThreadedSparseUploader gpuHumanRotationData; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public unsafe void Execute(int index) { var rig = newRigs[index]; ref var rdb = ref rig.rigBlob.Value; #if RUKHANKA_DEBUG_INFO for (var i = 0; i < rdb.bones.Length; ++i) { ref var b = ref rdb.bones[i]; var grp = new GPUStructures.BoneTransform() { pos = b.refPose.pos, rot = b.refPose.rot.value, scale = b.refPose.scale }; var rb = new GPUStructures.RigBone() { hash = b.hash, refPose = grp, humanBodyPart = (int)b.humanBodyPart, parentBoneIndex = b.parentBoneIndex }; var rigBoneIndex = rig.rigBonesOffset + i; var dstRigBoneOffsetInBytes = rigBoneIndex * UnsafeUtility.SizeOf(); gpuRigBones.AddUpload(rb, dstRigBoneOffsetInBytes); } #else var srcPtr = rdb.bones.GetUnsafePtr(); var srcOffset = rig.rigBonesOffset * UnsafeUtility.SizeOf(); var srcSize = rdb.bones.Length * UnsafeUtility.SizeOf(); gpuRigBones.AddUpload(srcPtr, srcSize, srcOffset); #endif if (rig.humanRotationDataOffset >= 0) { BurstAssert.IsTrue(UnsafeUtility.SizeOf() == UnsafeUtility.SizeOf(), "Size must match"); BurstAssert.IsTrue(rig.rigBlob.Value.bones.Length == rig.rigBlob.Value.humanData.Value.humanRotData.Length, "Count must match"); var srcHRDPtr = rdb.humanData.Value.humanRotData.GetUnsafePtr(); var srcHRDOffset = rig.humanRotationDataOffset * UnsafeUtility.SizeOf(); var srcHRDSize = rdb.humanData.Value.humanRotData.Length * UnsafeUtility.SizeOf(); gpuHumanRotationData.AddUpload(srcHRDPtr, srcHRDSize, srcHRDOffset); } var gpuRig = new GPUStructures.RigDefinition() { hash = rdb.hash.Value, rigBonesRange = new int2(rig.rigBonesOffset, rdb.bones.Length), rootBoneIndex = rdb.rootBoneIndex, humanRotationDataOffset = rig.humanRotationDataOffset }; var dstRigDefOffsetInBytes = rig.rigDefinitionIndex * UnsafeUtility.SizeOf(); gpuRigDefs.AddUpload(gpuRig, dstRigDefOffsetInBytes); } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct CopyNewSkinnedMeshesDataJob: IJobFor { [ReadOnly] public NativeList newSkinnedMeshData; public ThreadedSparseUploader gpuSkinnedMeshBonesData; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute(int index) { var brt = newSkinnedMeshData[index]; BurstAssert.IsTrue ( brt.skinnedMeshInfo.Value.bones.Length == brt.boneRemapTableBlob.Value.remapIndices.Length, "Skinned mesh bones count should be equal as remap indices count for this mesh" ); for (var i = 0; i < brt.skinnedMeshInfo.Value.bones.Length; ++i) { ref var bi = ref brt.skinnedMeshInfo.Value.bones[i]; var smbd = new GPUStructures.SkinnedMeshBoneData() { boneRemapIndex = brt.boneRemapTableBlob.Value.remapIndices[i] }; // Last column of bind pose is (0, 0, 0, 1). Do not store it smbd.bindPose.c0 = bi.bindPose.c0.xyz; smbd.bindPose.c1 = bi.bindPose.c1.xyz; smbd.bindPose.c2 = bi.bindPose.c2.xyz; smbd.bindPose.c3 = bi.bindPose.c3.xyz; var dstOffsetInBytes = (brt.dataOffset + i) * UnsafeUtility.SizeOf(); gpuSkinnedMeshBonesData.AddUpload(smbd, dstOffsetInBytes); } } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct FillFrameAnimatedRigWorkloadBuffersJob: IJobChunk { [NativeDisableParallelForRestriction] public NativeArray animatedBonesWorkloadBuf; [NativeDisableParallelForRestriction] public NativeArray frameRigAnimationJobs; [NativeDisableParallelForRestriction] public NativeArray animationToProcessBuf; [ReadOnly] public NativeParallelHashMap animationClipsOffsets; [ReadOnly] public NativeParallelHashMap avatarMasksOffsets; [ReadOnly] public NativeParallelHashMap rigDefinitionOffsets; [ReadOnly] public ComponentTypeHandle rigDefComponentTypeHandle; [ReadOnly] public BufferTypeHandle atpBufTypeHandle; [ReadOnly] public EntityTypeHandle entityTypeHandle; [ReadOnly] public ComponentTypeHandle frameOffsetsTypeHandle; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var rigDefs = chunk.GetNativeArray(ref rigDefComponentTypeHandle); var atpBufs = chunk.GetBufferAccessorRO(ref atpBufTypeHandle); var frameOffsets = chunk.GetNativeArray(ref frameOffsetsTypeHandle); var entities = chunk.GetNativeArray(entityTypeHandle); var frameOffsetsInChunk = chunk.GetChunkComponentData(ref frameOffsetsTypeHandle); var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); while (cee.NextEntityIndex(out var i)) { var rdc = rigDefs[i]; var atps = atpBufs[i]; var e = entities[i]; var frameOffset = frameOffsets[i]; frameOffset.AddOffsets(frameOffsetsInChunk); Execute(e, rdc, atps, frameOffset); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Execute(Entity e, in RigDefinitionComponent rdc, in DynamicBuffer atps, in GPURigFrameOffsetsComponent frameOffsets) { var abCount = rdc.rigBlob.Value.bones.Length; var atpCount = atps.Length; for (var i = 0; i < atpCount; ++i) { var atp = atps[i]; if (!atp.animation.IsCreated) continue; var gpuAtp = new GPUStructures.AnimationToProcess() { time = atp.time, weight = atp.weight, animationClipAddress = animationClipsOffsets[atp.animation.Value.hash], blendMode = (int)atp.blendMode, layerIndex = atp.layerIndex, layerWeight = atp.layerWeight, avatarMaskDataOffset = atp.avatarMask.IsCreated ? avatarMasksOffsets[atp.avatarMask.Value.hash] : -1 }; var idx = i + frameOffsets.animationToProcessIndex; animationToProcessBuf[idx] = gpuAtp; } if (!rigDefinitionOffsets.TryGetValue(rdc.rigBlob.Value.hash, out var rigDefIndex)) { return; } var faj = new GPUStructures.AnimationJob() { rigDefinitionIndex = rigDefIndex, animatedBoneIndexOffset = frameOffsets.boneIndex, animationsToProcessRange = new int2(frameOffsets.animationToProcessIndex, atpCount) }; frameRigAnimationJobs[frameOffsets.rigIndex] = faj; var abw = new GPUStructures.AnimatedBoneWorkload() { animationJobIndex = frameOffsets.rigIndex, }; for (var i = 0; i < abCount; ++i) { abw.boneIndexInRig = i; animatedBonesWorkloadBuf[frameOffsets.boneIndex + i] = abw; } } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct FillFrameSkinMatrixWorkloadBuffersJob: IJobChunk { [ReadOnly] public ComponentLookup gpuAnimationEngineTagLookup; [ReadOnly] public ComponentLookup rigDefLookup; [ReadOnly] public ComponentLookup frameOffsetsLookup; [ReadOnly] public ComponentLookup localToWorldLookup; [ReadOnly] public NativeParallelHashMap skinnedMeshDataMap; [ReadOnly] public NativeParallelHashMap entityToSMRFrameDataMap; [NativeDisableParallelForRestriction] public NativeArray frameSkinMatrixWorkloadBuf; [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 frameSkinnedMeshesAtomicCounter; [ReadOnly] public ComponentTypeHandle smrTypeHandle; [ReadOnly] public ComponentTypeHandle l2wTypeHandle; [ReadOnly] public EntityTypeHandle entityTypeHandle; [ReadOnly] public ComponentTypeHandle gpuRigChunkDataTypeHandle; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var smrs = chunk.GetNativeArray(ref smrTypeHandle); var l2ws = chunk.GetNativeArray(ref l2wTypeHandle); var entities = chunk.GetNativeArray(entityTypeHandle); var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); while (cee.NextEntityIndex(out var i)) { var smr = smrs[i]; var l2w = l2ws[i]; var e = entities[i]; var rigChunkIndex = chunk.m_EntityComponentStore->GetChunk(smr.animatedRigEntity); var rigChunk = new ArchetypeChunk(rigChunkIndex, chunk.m_EntityComponentStore); Execute(e, smr, l2w, rigChunk); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Execute(Entity e, in SkinnedMeshRendererComponent smr, in LocalToWorld l2w, in ArchetypeChunk rigChunk) { if (!smr.IsGPUAnimator(gpuAnimationEngineTagLookup)) return; if (!entityToSMRFrameDataMap.TryGetValue(e, out var skinMatrixData)) return; if (!rigDefLookup.TryGetComponent(smr.animatedRigEntity, out var rigDef)) return; var hash = AnimationUtils.CalculateBoneRemapTableHash(smr.smrInfoBlob, rigDef.rigBlob); if (!skinnedMeshDataMap.TryGetValue(hash, out var remapTableOffset)) { BurstAssert.IsTrue(false, "Skinned mesh to rig remap table is not found"); return; } var gpuRigFrameOffsets = rigChunk.GetChunkComponentData(ref gpuRigChunkDataTypeHandle); frameOffsetsLookup.TryGetComponent(smr.animatedRigEntity, out var frameOffsets); frameOffsets.AddOffsets(gpuRigFrameOffsets); var animatedEntityL2W = localToWorldLookup[smr.animatedRigEntity]; var invAnimatedEntityPose = math.inverse(animatedEntityL2W.Value); var smrPose = l2w.Value; var rootBoneToEntityTransform = math.mul(invAnimatedEntityPose, smrPose); var smw = new GPUStructures.SkinnedMeshWorkload() { skinMatricesCount = smr.smrInfoBlob.Value.bones.Length, boneRemapTableIndex = remapTableOffset, skinMatrixBaseOutIndex = skinMatrixData.skinMatrixIndex, rootBoneIndex = smr.rootBoneIndexInRig, animatedBoneIndexOffset = frameOffsets.boneIndex, skinnedMeshInverseTransform = math.inverse(rootBoneToEntityTransform) }; var index = frameSkinnedMeshesAtomicCounter.Add(1); frameSkinMatrixWorkloadBuf[index] = smw; } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct ComputeFrameRigWorkloadSizesPerChunkJob: IJobChunk { [ReadOnly] public ComponentTypeHandle rigDefComponentTypeHandle; [ReadOnly] public BufferTypeHandle atpBufTypeHandle; public ComponentTypeHandle frameOffsetsTypeHandle; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var rigDefs = chunk.GetNativeArray(ref rigDefComponentTypeHandle); var atpBufs = chunk.GetBufferAccessorRO(ref atpBufTypeHandle); var frameOffsets = chunk.GetNativeArray(ref frameOffsetsTypeHandle); var gpuRigFrameOffsets = new GPURigFrameOffsetsComponent(); var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); while (cee.NextEntityIndex(out var i)) { var rdc = rigDefs[i]; var atps = atpBufs[i]; frameOffsets[i] = gpuRigFrameOffsets; gpuRigFrameOffsets.boneIndex += rdc.rigBlob.Value.bones.Length; gpuRigFrameOffsets.rigIndex += 1; gpuRigFrameOffsets.animationToProcessIndex += atps.Length; } chunk.SetChunkComponentData(ref frameOffsetsTypeHandle, gpuRigFrameOffsets); } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] unsafe struct ComputeFrameRigWorkloadSizesAbsChunkOffsetsJob: IJobChunk { [NativeDisableUnsafePtrRestriction] public uint* frameAnimatedBonesCounter; [NativeDisableUnsafePtrRestriction] public uint* frameAnimationToProcessCounter; [NativeDisableUnsafePtrRestriction] public uint* frameAnimatedRigsCounter; public ComponentTypeHandle gpuRigChunkDataTypeHandle; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var gpuRigChunkData = chunk.GetChunkComponentData(ref gpuRigChunkDataTypeHandle); var gcd = gpuRigChunkData; gpuRigChunkData.boneIndex = (int)*frameAnimatedBonesCounter; gpuRigChunkData.animationToProcessIndex = (int)*frameAnimationToProcessCounter; gpuRigChunkData.rigIndex = (int)*frameAnimatedRigsCounter; *frameAnimatedBonesCounter += (uint)gcd.boneIndex; *frameAnimationToProcessCounter += (uint)gcd.animationToProcessIndex;; *frameAnimatedRigsCounter += (uint)gcd.rigIndex; chunk.SetChunkComponentData(ref gpuRigChunkDataTypeHandle, gpuRigChunkData); } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] partial struct ComputeFrameSkinnedMeshWorkloadSizesJob: IJobEntity { [ReadOnly] public ComponentLookup gpuAnimationEngineTagLookup; [NativeDisableParallelForRestriction] public NativeList frameSkinnedMeshesPerThreadCounters; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Execute(in SkinnedMeshRendererComponent asmc) { if (!asmc.IsGPUAnimator(gpuAnimationEngineTagLookup)) return; frameSkinnedMeshesPerThreadCounters[JobsUtility.ThreadIndex] += 1; } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] unsafe struct ComputeFrameSkinnedMeshWorkloadSizesTotalJob: IJob { [ReadOnly] public NativeList frameSkinnedMeshesPerThreadCounters; [NativeDisableUnsafePtrRestriction] public uint* frameSkinnedMeshesCounter; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute() { foreach (var c in frameSkinnedMeshesPerThreadCounters) { *frameSkinnedMeshesCounter += c; } } } //-----------------------------------------------------------------------------------------------------------------// [BurstCompile] struct ResetFrameDataJob: IJob { [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 frameAnimatedBonesCounter; [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 frameAnimatedRigsCounter; [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 frameAnimationToProcessCounter; [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 frameSkinnedMeshesCounter; public NativeList frameSkinnedMeshesPerThreadCounters; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Execute() { frameAnimatedBonesCounter.Reset(0); frameAnimatedRigsCounter.Reset(0); frameSkinnedMeshesCounter.Reset(0); frameAnimationToProcessCounter.Reset(0); frameSkinnedMeshesPerThreadCounters.Clear(); frameSkinnedMeshesPerThreadCounters.Resize(JobsUtility.ThreadIndexCount, NativeArrayOptions.ClearMemory); } } } }