using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; using UnityEngine; using Rukhanka.Toolbox; using Unity.Assertions; using Unity.Rendering; using Unity.Transforms; using Hash128 = Unity.Entities.Hash128; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka { [UpdateInGroup(typeof(RukhankaDeformationSystemGroup))] [UpdateAfter(typeof(SkinnedMeshPreparationSystem))] [UpdateBefore(typeof(MeshDeformationSystem))] public partial class GPUAnimationSystem: SystemBase { ComputeShader animationEngineCS; // Buffers for persistent GPU data. Equivalent for blob asset store GraphicsBuffer animationClipsGB; GraphicsBuffer avatarMasksGB; GraphicsBuffer rigDefinitionGB; GraphicsBuffer rigHumanAvatarRotationDataGB; GraphicsBuffer rigBonesGB; GraphicsBuffer skinnedMeshBoneDataGB; SparseUploaderPool sparseUploaderPool; // Buffers for per frame animation data (workload, animation to process). Equivalent for animation components FrameFencedGPUBufferPool frameAnimationToProcessGB; FrameFencedGPUBufferPool framePerBoneAnimationWorkloadGB; FrameFencedGPUBufferPool frameRigAnimationJobsGB; FrameFencedGPUBufferPool frameSkinMatrixWorkloadGB; // Buffers filled by compute shaders GraphicsBuffer animatedBonesGB; GraphicsBuffer rigSpaceAnimatedBonesGB; ComputeKernel processAnimationKernel; ComputeKernel makeRigSpaceBoneTransformsKernel; ComputeKernel makeSkinMatricesKernel; ComputeKernel copyBufferKernel; ComputeKernel copyMeshDataKernel; NativeParallelHashMap> skinnedMeshToRigBoneRemapTables; EntityQuery gpuAnimatedRigQuery, gpuAnimatedRigNoChunkComponentsQuery, smrQuery; #if RUKHANKA_DEBUG_INFO BoneVisualizationSystem boneVisualizationSystem; #endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override void OnCreate() { if (!SystemInfo.supportsComputeShaders) { Debug.LogWarning("Compute shaders is not supported on current platform. GPU animation engine is disabled."); Enabled = false; return; } #if !HYBRID_RENDERER_DISABLED if (!EntitiesGraphicsUtils.IsEntitiesGraphicsSupportedOnSystem()) #endif { Enabled = false; return; } skinnedMeshToRigBoneRemapTables = new (0xff, Allocator.Persistent); CreateComputeBuffers(); animationEngineCS = Resources.Load("GPUAnimationEngine"); processAnimationKernel = new ComputeKernel(animationEngineCS, "ProcessAnimations"); makeRigSpaceBoneTransformsKernel = new ComputeKernel(animationEngineCS, "MakeRigSpaceBoneTransforms"); makeSkinMatricesKernel = new ComputeKernel(animationEngineCS, "ComputeSkinMatrices"); gpuAnimatedRigQuery = new EntityQueryBuilder(Allocator.Temp) .WithAll() .WithAllChunkComponent() .WithNone() .Build(EntityManager); gpuAnimatedRigNoChunkComponentsQuery = new EntityQueryBuilder(Allocator.Temp) .WithAll() .WithNoneChunkComponent() .WithNone() .Build(EntityManager); smrQuery = new EntityQueryBuilder(Allocator.Temp) .WithAll() .Build(EntityManager); Assert.IsTrue(UnsafeUtility.SizeOf() == UnsafeUtility.SizeOf()); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override void OnUpdate() { ref var runtimeAnimationData = ref SystemAPI.GetSingletonRW().ValueRW; EntityManager.AddChunkComponentData(gpuAnimatedRigNoChunkComponentsQuery, default); PrepareFrameAnimationData(ref runtimeAnimationData, Dependency); DispatchAnimationCalculation(ref runtimeAnimationData); SetBuffersForBoneRenderer(ref runtimeAnimationData); GPUBuffersEndFrame(); sparseUploaderPool.FrameCleanup(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override void OnDestroy() { animationClipsGB?.Release(); avatarMasksGB?.Release(); rigDefinitionGB?.Release(); rigHumanAvatarRotationDataGB?.Release(); rigBonesGB?.Release(); skinnedMeshBoneDataGB?.Release(); sparseUploaderPool?.Dispose(); frameAnimationToProcessGB?.Dispose(); framePerBoneAnimationWorkloadGB?.Dispose(); frameRigAnimationJobsGB?.Dispose(); frameSkinMatrixWorkloadGB?.Dispose(); animatedBonesGB?.Dispose(); rigSpaceAnimatedBonesGB?.Dispose(); if (skinnedMeshToRigBoneRemapTables.IsCreated) { foreach (var remapTable in skinnedMeshToRigBoneRemapTables) { remapTable.Value.Dispose(); } } skinnedMeshToRigBoneRemapTables.Dispose(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateComputeBuffers() { var bufferInitialCapacity = 0x4; frameAnimationToProcessGB = new (bufferInitialCapacity, GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.LockBufferForWrite); framePerBoneAnimationWorkloadGB = new (bufferInitialCapacity, GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.LockBufferForWrite); frameRigAnimationJobsGB = new (bufferInitialCapacity, GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.LockBufferForWrite); frameSkinMatrixWorkloadGB = new (bufferInitialCapacity, GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.LockBufferForWrite); animatedBonesGB = new (GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.None, bufferInitialCapacity, UnsafeUtility.SizeOf()); rigSpaceAnimatedBonesGB = new (GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.None, bufferInitialCapacity, UnsafeUtility.SizeOf()); // Persistent data buffers var persistentBuffersInitialCapacity = 0x4; animationClipsGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); avatarMasksGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); rigDefinitionGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); rigHumanAvatarRotationDataGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); rigBonesGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); skinnedMeshBoneDataGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf()); sparseUploaderPool = new SparseUploaderPool(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe JobHandle CalculateFrameWorksetData(ref GPURuntimeAnimationData rad, JobHandle dependsOn) { // All following jobs should be sequential. Interlocked math for a few counters are significantly slower than // sequential execution var gpuRigFrameOffsetsTypeHandle = SystemAPI.GetComponentTypeHandle(); var rigDefTypeHandle = SystemAPI.GetComponentTypeHandle(true); var atpBufHandle = SystemAPI.GetBufferTypeHandle(true); // Workload sizes for animation calculation per chunk var computeRigsWorkloadSizesJob = new ComputeFrameRigWorkloadSizesPerChunkJob() { frameOffsetsTypeHandle = gpuRigFrameOffsetsTypeHandle, atpBufTypeHandle = atpBufHandle, rigDefComponentTypeHandle = rigDefTypeHandle, }; var computeRigsWorkloadSizesJH = computeRigsWorkloadSizesJob.ScheduleParallel(gpuAnimatedRigQuery, dependsOn); // Make absolute chunk offsets var computeAbsChunkOffsetsJob = new ComputeFrameRigWorkloadSizesAbsChunkOffsetsJob() { frameAnimatedBonesCounter = (uint*)UnsafeUtility.AddressOf(ref rad.frameAnimatedBonesCounter), frameAnimatedRigsCounter = (uint*)UnsafeUtility.AddressOf(ref rad.frameAnimatedRigsCounter), frameAnimationToProcessCounter = (uint*)UnsafeUtility.AddressOf(ref rad.frameAnimationToProcessCounter), gpuRigChunkDataTypeHandle = gpuRigFrameOffsetsTypeHandle }; var computeAbsChunkOffsetsJH = computeAbsChunkOffsetsJob.Schedule(gpuAnimatedRigQuery, computeRigsWorkloadSizesJH); // Workload sizes for skin matrices calculation // To avoid InterlockedAdd (it is slow when invocation count is really big), and single thread job I use 2-step // process here: first job increments per-thread counters, and another single thread job do final accumulate. var computeSkinnedMeshCountJob = new ComputeFrameSkinnedMeshWorkloadSizesJob() { frameSkinnedMeshesPerThreadCounters = rad.frameSkinnedMeshesPerThreadCounters, gpuAnimationEngineTagLookup = SystemAPI.GetComponentLookup(true) }; var computeSkinnedMeshCountTotalJob = new ComputeFrameSkinnedMeshWorkloadSizesTotalJob() { frameSkinnedMeshesPerThreadCounters = rad.frameSkinnedMeshesPerThreadCounters, frameSkinnedMeshesCounter = (uint*)UnsafeUtility.AddressOf(ref rad.frameSkinnedMeshesCounter) }; var computeSkinnedMeshCountJH = computeSkinnedMeshCountJob.ScheduleParallel(dependsOn); var computeSkinnedMeshCountTotalJH = computeSkinnedMeshCountTotalJob.Schedule(computeSkinnedMeshCountJH); var rv = JobHandle.CombineDependencies(computeAbsChunkOffsetsJH, computeSkinnedMeshCountTotalJH); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void GPUBuffersBeginFrame() { frameAnimationToProcessGB.BeginFrame(); framePerBoneAnimationWorkloadGB.BeginFrame(); frameRigAnimationJobsGB.BeginFrame(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void GPUBuffersEndFrame() { frameAnimationToProcessGB.EndFrame(); framePerBoneAnimationWorkloadGB.EndFrame(); frameRigAnimationJobsGB.EndFrame(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe void FillFrameSkinMatrixCalculationWorkloadGPUBuffers ( ref GPURuntimeAnimationData rad, in NativeParallelHashMap entityToSMRFrameDataMap ) { var frameSkinMatrixWorkloadBuf = frameSkinMatrixWorkloadGB.LockBufferForWrite(0, (int)rad.frameSkinnedMeshesCounter); rad.frameSkinnedMeshesCounter = 0; var frameSkinnedMeshesAtomicCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.frameSkinnedMeshesCounter)); var fillFrameSkinMatrixWorkloadBuffersJob = new FillFrameSkinMatrixWorkloadBuffersJob() { frameSkinnedMeshesAtomicCounter = frameSkinnedMeshesAtomicCounter, frameSkinMatrixWorkloadBuf = frameSkinMatrixWorkloadBuf, skinnedMeshDataMap = rad.skinnedMeshesDataMap, rigDefLookup = SystemAPI.GetComponentLookup(true), gpuAnimationEngineTagLookup = SystemAPI.GetComponentLookup(true), localToWorldLookup = SystemAPI.GetComponentLookup(true), entityToSMRFrameDataMap = entityToSMRFrameDataMap, gpuRigChunkDataTypeHandle = SystemAPI.GetComponentTypeHandle(true), smrTypeHandle = SystemAPI.GetComponentTypeHandle(true), l2wTypeHandle = SystemAPI.GetComponentTypeHandle(true), entityTypeHandle = SystemAPI.GetEntityTypeHandle(), frameOffsetsLookup = SystemAPI.GetComponentLookup(true) }; fillFrameSkinMatrixWorkloadBuffersJob.ScheduleParallel(smrQuery, default).Complete(); frameSkinMatrixWorkloadGB.UnlockBufferAfterWrite((int)rad.frameSkinnedMeshesCounter); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void FillFrameAnimationCalculationWorkloadGPUBuffers(ref GPURuntimeAnimationData rad) { var animatedBonesWorkloadBuf = framePerBoneAnimationWorkloadGB.LockBufferForWrite(0, (int)rad.frameAnimatedBonesCounter); var animationToProcessBuf = frameAnimationToProcessGB.LockBufferForWrite(0, (int)rad.frameAnimationToProcessCounter); var rigAnimationJobsBuf = frameRigAnimationJobsGB.LockBufferForWrite(0, (int)rad.frameAnimatedRigsCounter); // Fill GPU buffer data for current frame animation calculation var fillFrameAnimatedRigWorkloadDataJob = new FillFrameAnimatedRigWorkloadBuffersJob() { animatedBonesWorkloadBuf = animatedBonesWorkloadBuf, animationToProcessBuf = animationToProcessBuf, frameRigAnimationJobs = rigAnimationJobsBuf, animationClipsOffsets = rad.animationClipsMap, rigDefinitionOffsets = rad.rigDefinitionsMap, avatarMasksOffsets = rad.avatarMasksDataMap, frameOffsetsTypeHandle = SystemAPI.GetComponentTypeHandle(true), atpBufTypeHandle = SystemAPI.GetBufferTypeHandle(true), rigDefComponentTypeHandle = SystemAPI.GetComponentTypeHandle(true), entityTypeHandle = SystemAPI.GetEntityTypeHandle() }; fillFrameAnimatedRigWorkloadDataJob.ScheduleParallel(gpuAnimatedRigQuery, new JobHandle()).Complete(); framePerBoneAnimationWorkloadGB.UnlockBufferAfterWrite((int)rad.frameAnimatedBonesCounter); frameAnimationToProcessGB.UnlockBufferAfterWrite((int)rad.frameAnimationToProcessCounter); frameRigAnimationJobsGB.UnlockBufferAfterWrite((int)rad.frameAnimatedRigsCounter); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe JobHandle ResetFrameData(ref GPURuntimeAnimationData rad, JobHandle dependsOn) { var resetFrameDataJob = new ResetFrameDataJob() { frameAnimatedBonesCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.frameAnimatedBonesCounter)), frameAnimatedRigsCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.frameAnimatedRigsCounter)), frameSkinnedMeshesCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.frameSkinnedMeshesCounter)), frameAnimationToProcessCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.frameAnimationToProcessCounter)), frameSkinnedMeshesPerThreadCounters = rad.frameSkinnedMeshesPerThreadCounters }; var rv = resetFrameDataJob.Schedule(dependsOn); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void PrepareFrameAnimationData(ref GPURuntimeAnimationData runtimeAnimationData, JobHandle dependsOn) { var resetFrameDataJH = ResetFrameData(ref runtimeAnimationData, dependsOn); var calculateFrameWorksetDataJH = CalculateFrameWorksetData(ref runtimeAnimationData, resetFrameDataJH); // Complete dependency because need to resize GPU buffers with actualized frame counts calculateFrameWorksetDataJH.Complete(); ResizeGPUBuffers(ref runtimeAnimationData); CopyPersistentAnimationDataToGPUBuffers(ref runtimeAnimationData); GPUBuffersBeginFrame(); FillFrameAnimationCalculationWorkloadGPUBuffers(ref runtimeAnimationData); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CopyPersistentAnimationDataToGPUBuffers(ref GPURuntimeAnimationData rad) { CopyNewAnimationsToGPUBuffers(ref rad); CopyNewRigsToGPUBuffer(ref rad); CopyNewBoneRemapTablesToGPUBuffers(ref rad); CopyNewAvatarMasksToGPUBuffer(ref rad); //DebugBufRead(ref rad); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// GraphicsBuffer GrowPersistentDataComputeBuffer(GraphicsBuffer gb, uint newElementCount) { if (gb == null || gb.count >= newElementCount) return gb; var newBuf = new GraphicsBuffer(gb.target, gb.usageFlags, (int)newElementCount, gb.stride); ComputeBufferTools.Copy(gb, newBuf, 0, 0, (uint)(newElementCount * gb.stride)); gb.Dispose(); return newBuf; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ResizeGPUBuffers(ref GPURuntimeAnimationData rad) { // Persistent data buffers animationClipsGB = GrowPersistentDataComputeBuffer(animationClipsGB, rad.totalGPUAnimationClipsSize.x / 4); avatarMasksGB = GrowPersistentDataComputeBuffer(avatarMasksGB, rad.totalGPUAvatarMasksDataCount.x); rigDefinitionGB = GrowPersistentDataComputeBuffer(rigDefinitionGB, rad.totalGPURigsCount.x); rigHumanAvatarRotationDataGB = GrowPersistentDataComputeBuffer(rigHumanAvatarRotationDataGB, rad.totalGPUHumanRotationDataEntriesCount.x); rigBonesGB = GrowPersistentDataComputeBuffer(rigBonesGB, rad.totalGPURigBonesCount.x); skinnedMeshBoneDataGB = GrowPersistentDataComputeBuffer(skinnedMeshBoneDataGB, rad.totalGPUSkinnedMeshBonesCount.x); // Everyframe updated buffers frameAnimationToProcessGB.Grow((int)rad.frameAnimationToProcessCounter); framePerBoneAnimationWorkloadGB.Grow((int)rad.frameAnimatedBonesCounter); frameRigAnimationJobsGB.Grow((int)rad.frameAnimatedRigsCounter); frameSkinMatrixWorkloadGB.Grow((int)rad.frameSkinnedMeshesCounter); animatedBonesGB = ComputeBufferTools.GrowNoCopy(animatedBonesGB, (int)rad.frameAnimatedBonesCounter); rigSpaceAnimatedBonesGB = ComputeBufferTools.GrowNoCopy(rigSpaceAnimatedBonesGB, (int)rad.frameAnimatedBonesCounter); Shader.SetGlobalBuffer(ShaderID_rigSpaceBoneTransformsBuf, rigSpaceAnimatedBonesGB); Shader.SetGlobalBuffer(ShaderID_boneLocalTransforms, animatedBonesGB); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CopyNewBoneRemapTablesToGPUBuffers(ref GPURuntimeAnimationData rad) { if (rad.newSkinnedMeshesDataList.IsEmpty) return; var newSkinnedMeshBonesCount = (int)(rad.totalGPUSkinnedMeshBonesCount.x - rad.totalGPUSkinnedMeshBonesCount.y); var skinnedMeshBonesUploader = sparseUploaderPool.GetUploader(skinnedMeshBoneDataGB); var skinnedMeshBonesUploadSizeInBytes = newSkinnedMeshBonesCount * UnsafeUtility.SizeOf(); var skinnedMeshBonesThreadedUploader = skinnedMeshBonesUploader.Begin(skinnedMeshBonesUploadSizeInBytes, skinnedMeshBonesUploadSizeInBytes, newSkinnedMeshBonesCount); var copyNewBoneRemapTables = new CopyNewSkinnedMeshesDataJob() { newSkinnedMeshData = rad.newSkinnedMeshesDataList, gpuSkinnedMeshBonesData = skinnedMeshBonesThreadedUploader, }; copyNewBoneRemapTables.ScheduleParallel(rad.newSkinnedMeshesDataList.Length, 1, default).Complete(); skinnedMeshBonesUploader.EndAndCommit(skinnedMeshBonesThreadedUploader); sparseUploaderPool.PutUploader(skinnedMeshBonesUploader); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CopyNewAvatarMasksToGPUBuffer(ref GPURuntimeAnimationData rad) { if (rad.newAvatarMasksList.IsEmpty) return; var newAvatarMasksEntriesCount = (int)(rad.totalGPUAvatarMasksDataCount.x - rad.totalGPUAvatarMasksDataCount.y); var avatarMaskUploader = sparseUploaderPool.GetUploader(avatarMasksGB); var avatarMaskUploadSizeInBytes = newAvatarMasksEntriesCount * UnsafeUtility.SizeOf(); var avatarMaskThreadedUploader = avatarMaskUploader.Begin(avatarMaskUploadSizeInBytes, avatarMaskUploadSizeInBytes, newAvatarMasksEntriesCount); var copyNewAvatarMasksToGPUJob = new CopyNewAvatarMasksToGPUJob() { gpuAvatarMasks = avatarMaskThreadedUploader, newAvatarMasks = rad.newAvatarMasksList }; copyNewAvatarMasksToGPUJob.ScheduleParallel(rad.newAvatarMasksList.Length, 1, default).Complete(); avatarMaskUploader.EndAndCommit(avatarMaskThreadedUploader); sparseUploaderPool.PutUploader(avatarMaskUploader); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CopyNewRigsToGPUBuffer(ref GPURuntimeAnimationData rad) { if (rad.newRigsList.IsEmpty) return; var newRigBonesCount = (int)(rad.totalGPURigBonesCount.x - rad.totalGPURigBonesCount.y); var newRigsCount = (int)(rad.totalGPURigsCount.x - rad.totalGPURigsCount.y); var newHumanRotationDataEntriesCount = (int)(rad.totalGPUHumanRotationDataEntriesCount.x - rad.totalGPUHumanRotationDataEntriesCount.y); var rigBonesUploader = sparseUploaderPool.GetUploader(rigBonesGB); var rigBonesUploadSizeInBytes = newRigBonesCount * UnsafeUtility.SizeOf(); var rigBonesThreadedUploader = rigBonesUploader.Begin(rigBonesUploadSizeInBytes, rigBonesUploadSizeInBytes, newRigBonesCount); var rigDefinitionUploader = sparseUploaderPool.GetUploader(rigDefinitionGB); var rigDefinitionsUploadSizeInBytes = newRigsCount * UnsafeUtility.SizeOf(); var rigDefinitionsThreadedUploader = rigDefinitionUploader.Begin(rigDefinitionsUploadSizeInBytes, rigDefinitionsUploadSizeInBytes, newRigsCount); var rigHumanRotationDataUploader = sparseUploaderPool.GetUploader(rigHumanAvatarRotationDataGB); var rigHumanRotationDataUploadSizeInBytes = newHumanRotationDataEntriesCount * UnsafeUtility.SizeOf(); var rigHumanRotationDataThreadedUploader = rigHumanRotationDataUploader.Begin(rigHumanRotationDataUploadSizeInBytes, rigHumanRotationDataUploadSizeInBytes, newRigsCount); var copyNewRigsToGPUJob = new CopyNewRigsToGPUJob() { newRigs = rad.newRigsList, gpuRigBones = rigBonesThreadedUploader, gpuRigDefs = rigDefinitionsThreadedUploader, gpuHumanRotationData = rigHumanRotationDataThreadedUploader }; copyNewRigsToGPUJob.ScheduleParallel(rad.newRigsList.Length, 1, default).Complete(); rigDefinitionUploader.EndAndCommit(rigDefinitionsThreadedUploader); rigBonesUploader.EndAndCommit(rigBonesThreadedUploader); rigHumanRotationDataUploader.EndAndCommit(rigHumanRotationDataThreadedUploader); sparseUploaderPool.PutUploader(rigDefinitionUploader); sparseUploaderPool.PutUploader(rigBonesUploader); sparseUploaderPool.PutUploader(rigHumanRotationDataUploader); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CopyNewAnimationsToGPUBuffers(ref GPURuntimeAnimationData rad) { if (rad.newAnimationsList.IsEmpty) return; var newAnimationClipsCount = rad.newAnimationsList.Length; // Very conservative value var maxNumUploadsPerClip = 0xf; var animationClipsUploader = sparseUploaderPool.GetUploader(animationClipsGB); var maxAnimationClipSingleUploadSize = rad.totalGPUAnimationClipsSize.x - rad.totalGPUAnimationClipsSize.y; var animationClipsThreadedUploader = animationClipsUploader.Begin((int)maxAnimationClipSingleUploadSize, (int)maxAnimationClipSingleUploadSize, newAnimationClipsCount * maxNumUploadsPerClip); var createGPUAnimationClip = new CopyNewAnimationsToGPUJob() { newAnimationClips = rad.newAnimationsList, gpuAnimationClips = animationClipsThreadedUploader, }; createGPUAnimationClip.ScheduleParallel(rad.newAnimationsList.Length, 1, default).Complete(); animationClipsUploader.EndAndCommit(animationClipsThreadedUploader); sparseUploaderPool.PutUploader(animationClipsUploader); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// internal void BuildSkinMatrices(in NativeParallelHashMap entityToSMRFrameDataMap, FrameFencedGPUBufferPool outSkinMatrixGB) { if (!SystemAPI.TryGetSingletonRW(out var radRW)) return; ref var runtimeAnimationData = ref radRW.ValueRW; frameSkinMatrixWorkloadGB.BeginFrame(); FillFrameSkinMatrixCalculationWorkloadGPUBuffers(ref runtimeAnimationData, entityToSMRFrameDataMap); if (runtimeAnimationData.frameSkinnedMeshesCounter > 0) { animationEngineCS.SetBuffer(makeSkinMatricesKernel, ShaderID_skinMatrixWorkloadBuf, frameSkinMatrixWorkloadGB); animationEngineCS.SetBuffer(makeSkinMatricesKernel, ShaderID_rigSpaceBoneTransformsBuf, rigSpaceAnimatedBonesGB); animationEngineCS.SetBuffer(makeSkinMatricesKernel, ShaderID_outSkinMatrices, outSkinMatrixGB); animationEngineCS.SetBuffer(makeSkinMatricesKernel, ShaderID_skinnedMeshBoneData, skinnedMeshBoneDataGB); animationEngineCS.SetInt(ShaderID_totalSkinnedMeshes, (int)runtimeAnimationData.frameSkinnedMeshesCounter); makeSkinMatricesKernel.Dispatch(runtimeAnimationData.frameSkinnedMeshesCounter, 1, 1); } frameSkinMatrixWorkloadGB.EndFrame(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void DispatchAnimationCalculation(ref GPURuntimeAnimationData rad) { if (rad.frameAnimatedBonesCounter == 0) return; // Animation computation animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_outAnimatedBones, animatedBonesGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_animatedBoneWorkload, framePerBoneAnimationWorkloadGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_animationJobs, frameRigAnimationJobsGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_animationsToProcess, frameAnimationToProcessGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_rigDefinitions, rigDefinitionGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_rigBones, rigBonesGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_animationClips, animationClipsGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_humanRotationDataBuffer, rigHumanAvatarRotationDataGB); animationEngineCS.SetBuffer(processAnimationKernel, ShaderID_avatarMasksBuffer, avatarMasksGB); animationEngineCS.SetInt(ShaderID_animatedBonesCount, (int)rad.frameAnimatedBonesCounter); processAnimationKernel.Dispatch(rad.frameAnimatedBonesCounter, 1, 1); // Compute rig space bone transforms animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_boneLocalTransforms, animatedBonesGB); animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_animatedBoneWorkload, framePerBoneAnimationWorkloadGB); animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_animationJobs, frameRigAnimationJobsGB); animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_rigDefinitions, rigDefinitionGB); animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_rigBones, rigBonesGB); animationEngineCS.SetBuffer(makeRigSpaceBoneTransformsKernel, ShaderID_outBoneTransforms, rigSpaceAnimatedBonesGB); makeRigSpaceBoneTransformsKernel.Dispatch(rad.frameAnimatedBonesCounter, 1, 1); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SetBuffersForBoneRenderer(ref GPURuntimeAnimationData rad) { #if RUKHANKA_DEBUG_INFO boneVisualizationSystem ??= World.GetExistingSystemManaged(); boneVisualizationSystem.gpuBoneRenderer.SetGPUBuffersForFrame ( framePerBoneAnimationWorkloadGB, frameRigAnimationJobsGB, rigDefinitionGB, rigBonesGB, rigSpaceAnimatedBonesGB, (int)rad.frameAnimatedBonesCounter ); #endif } } }