Files
Project-M/Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationSystem.cs
T
2026-05-31 14:27:52 -07:00

581 lines
28 KiB
C#

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<GPUStructures.AnimationToProcess> frameAnimationToProcessGB;
FrameFencedGPUBufferPool<GPUStructures.AnimatedBoneWorkload> framePerBoneAnimationWorkloadGB;
FrameFencedGPUBufferPool<GPUStructures.AnimationJob> frameRigAnimationJobsGB;
FrameFencedGPUBufferPool<GPUStructures.SkinnedMeshWorkload> frameSkinMatrixWorkloadGB;
// Buffers filled by compute shaders
GraphicsBuffer animatedBonesGB;
GraphicsBuffer rigSpaceAnimatedBonesGB;
ComputeKernel processAnimationKernel;
ComputeKernel makeRigSpaceBoneTransformsKernel;
ComputeKernel makeSkinMatricesKernel;
ComputeKernel copyBufferKernel;
ComputeKernel copyMeshDataKernel;
NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>> 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<ComputeShader>("GPUAnimationEngine");
processAnimationKernel = new ComputeKernel(animationEngineCS, "ProcessAnimations");
makeRigSpaceBoneTransformsKernel = new ComputeKernel(animationEngineCS, "MakeRigSpaceBoneTransforms");
makeSkinMatricesKernel = new ComputeKernel(animationEngineCS, "ComputeSkinMatrices");
gpuAnimatedRigQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<RigDefinitionComponent, AnimationToProcessComponent, GPUAnimationEngineTag>()
.WithAllChunkComponent<GPURigFrameOffsetsComponent>()
.WithNone<CullAnimationsTag>()
.Build(EntityManager);
gpuAnimatedRigNoChunkComponentsQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<RigDefinitionComponent, AnimationToProcessComponent, GPUAnimationEngineTag>()
.WithNoneChunkComponent<GPURigFrameOffsetsComponent>()
.WithNone<CullAnimationsTag>()
.Build(EntityManager);
smrQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<SkinnedMeshRendererComponent, LocalToWorld>()
.Build(EntityManager);
Assert.IsTrue(UnsafeUtility.SizeOf<GPUStructures.KeyFrame>() == UnsafeUtility.SizeOf<KeyFrame>());
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected override void OnUpdate()
{
ref var runtimeAnimationData = ref SystemAPI.GetSingletonRW<GPURuntimeAnimationData>().ValueRW;
EntityManager.AddChunkComponentData<GPURigFrameOffsetsComponent>(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<GPUStructures.BoneTransform>());
rigSpaceAnimatedBonesGB = new (GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.None, bufferInitialCapacity, UnsafeUtility.SizeOf<GPUStructures.BoneTransform>());
// Persistent data buffers
var persistentBuffersInitialCapacity = 0x4;
animationClipsGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<uint>());
avatarMasksGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<uint>());
rigDefinitionGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<GPUStructures.RigDefinition>());
rigHumanAvatarRotationDataGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<GPUStructures.HumanRotationData>());
rigBonesGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<GPUStructures.RigBone>());
skinnedMeshBoneDataGB = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, persistentBuffersInitialCapacity, UnsafeUtility.SizeOf<GPUStructures.SkinnedMeshBoneData>());
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<GPURigFrameOffsetsComponent>();
var rigDefTypeHandle = SystemAPI.GetComponentTypeHandle<RigDefinitionComponent>(true);
var atpBufHandle = SystemAPI.GetBufferTypeHandle<AnimationToProcessComponent>(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<GPUAnimationEngineTag>(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<Entity, SkinnedMeshRendererFrameDeformationData> 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<RigDefinitionComponent>(true),
gpuAnimationEngineTagLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true),
localToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(true),
entityToSMRFrameDataMap = entityToSMRFrameDataMap,
gpuRigChunkDataTypeHandle = SystemAPI.GetComponentTypeHandle<GPURigFrameOffsetsComponent>(true),
smrTypeHandle = SystemAPI.GetComponentTypeHandle<SkinnedMeshRendererComponent>(true),
l2wTypeHandle = SystemAPI.GetComponentTypeHandle<LocalToWorld>(true),
entityTypeHandle = SystemAPI.GetEntityTypeHandle(),
frameOffsetsLookup = SystemAPI.GetComponentLookup<GPURigFrameOffsetsComponent>(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<GPURigFrameOffsetsComponent>(true),
atpBufTypeHandle = SystemAPI.GetBufferTypeHandle<AnimationToProcessComponent>(true),
rigDefComponentTypeHandle = SystemAPI.GetComponentTypeHandle<RigDefinitionComponent>(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<GPUStructures.SkinnedMeshBoneData>();
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<uint>();
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<GPUStructures.RigBone>();
var rigBonesThreadedUploader = rigBonesUploader.Begin(rigBonesUploadSizeInBytes, rigBonesUploadSizeInBytes, newRigBonesCount);
var rigDefinitionUploader = sparseUploaderPool.GetUploader(rigDefinitionGB);
var rigDefinitionsUploadSizeInBytes = newRigsCount * UnsafeUtility.SizeOf<GPUStructures.RigDefinition>();
var rigDefinitionsThreadedUploader = rigDefinitionUploader.Begin(rigDefinitionsUploadSizeInBytes, rigDefinitionsUploadSizeInBytes, newRigsCount);
var rigHumanRotationDataUploader = sparseUploaderPool.GetUploader(rigHumanAvatarRotationDataGB);
var rigHumanRotationDataUploadSizeInBytes = newHumanRotationDataEntriesCount * UnsafeUtility.SizeOf<GPUStructures.HumanRotationData>();
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<Entity, SkinnedMeshRendererFrameDeformationData> entityToSMRFrameDataMap, FrameFencedGPUBufferPool<SkinMatrix> outSkinMatrixGB)
{
if (!SystemAPI.TryGetSingletonRW<GPURuntimeAnimationData>(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>();
boneVisualizationSystem.gpuBoneRenderer.SetGPUBuffersForFrame
(
framePerBoneAnimationWorkloadGB,
frameRigAnimationJobsGB,
rigDefinitionGB,
rigBonesGB,
rigSpaceAnimatedBonesGB,
(int)rad.frameAnimatedBonesCounter
);
#endif
}
}
}