Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,61 @@
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
// If present and enabled, then GPU animation engine is used
public struct GPUAnimationEngineTag: IComponentData, IEnableableComponent { }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct GPUAttachmentComponent: IComponentData
{
public int attachedBoneIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Used to move attachments using GPU computed bone poses in shader
[MaterialProperty("_RukhankaGPUBoneIndex")]
public struct GPUAttachmentBoneIndexMPComponent: IComponentData
{
public int boneIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MaterialProperty("_RukhankaAttachmentToBoneTransform")]
public struct GPUAttachmentToBoneTransformMPComponent: IComponentData
{
public float4x4 value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MaterialProperty("_RukhankaAnimatedEntityLocalToWorld")]
public struct GPURigEntityLocalToWorldMPComponent: IComponentData
{
public float4x4 value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct GPURigFrameOffsetsComponent: IComponentData
{
public int boneIndex;
public int rigIndex;
public int animationToProcessIndex;
public void AddOffsets(GPURigFrameOffsetsComponent o)
{
boneIndex += o.boneIndex;
rigIndex += o.rigIndex;
animationToProcessIndex += o.animationToProcessIndex;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 041a1589732bfa541891d94c0997f0fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationComponents.cs
uploadId: 897522
@@ -0,0 +1,255 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Rendering;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaDeformationSystemGroup))]
[UpdateBefore(typeof(GPUAnimationSystem))]
public partial struct GPUAnimationPreparationSystem: ISystem
{
NativeParallelHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> newAnimationsMap;
NativeParallelHashMap<Hash128, BlobAssetReference<RigDefinitionBlob>> newRigsMap;
NativeParallelHashMap<Hash128, GPUSkinnedMeshPlacementData> newSkinnedMeshesDataMap;
NativeParallelHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> newAvatarMaskMap;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void OnCreate(ref SystemState ss)
{
#if !HYBRID_RENDERER_DISABLED
if (!EntitiesGraphicsUtils.IsEntitiesGraphicsSupportedOnSystem())
#endif
{
ss.Enabled = false;
return;
}
var runtimeAnimationDataSingleton = GPURuntimeAnimationData.Construct();
ss.EntityManager.CreateSingleton(runtimeAnimationDataSingleton, "Rukhanka GPU Animation Data");
var initialCapacity = 0x1000;
newAnimationsMap = new (initialCapacity, Allocator.Persistent);
newRigsMap = new (initialCapacity, Allocator.Persistent);
newSkinnedMeshesDataMap = new (initialCapacity, Allocator.Persistent);
newAvatarMaskMap = new (initialCapacity, Allocator.Persistent);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
if (SystemAPI.TryGetSingletonRW<GPURuntimeAnimationData>(out var radRW))
{
ref var rad = ref radRW.ValueRW;
rad.Dispose();
ss.EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity<GPURuntimeAnimationData>());
}
newAnimationsMap.Dispose();
newRigsMap.Dispose();
newSkinnedMeshesDataMap.Dispose();
newAvatarMaskMap.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
ref var runtimeAnimationData = ref SystemAPI.GetSingletonRW<GPURuntimeAnimationData>().ValueRW;
var resetFrameDataJH = ResetFrameData(ref ss, ref runtimeAnimationData, ss.Dependency);
// Prepare new resources that appear this frame
var newAnimationResourcesJH = GatherNewRigsAndAnimations(ref ss, ref runtimeAnimationData, resetFrameDataJH);
var newRigRemapTablesJH = GatherNewRigRemapTables(ref ss, ref runtimeAnimationData, resetFrameDataJH);
var newResourcesJH = JobHandle.CombineDependencies(newAnimationResourcesJH, newRigRemapTablesJH);
// Register new resources in persistent db
var registerNewResourcesJH = RegisterNewResources(ref ss, ref runtimeAnimationData, newResourcesJH);
ss.Dependency = registerNewResourcesJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle ResetFrameData(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
var resetFrameDataJob = new ResetFrameDataJob()
{
newAnimationsMap = newAnimationsMap,
newRigsMap = newRigsMap,
newAnimationsList = rad.newAnimationsList,
newRigsList = rad.newRigsList,
newSkinnedMeshDataMap = newSkinnedMeshesDataMap,
newSkinnedMeshDataList = rad.newSkinnedMeshesDataList,
newAvatarMasksList = rad.newAvatarMasksList,
newAvatarMasksMap = newAvatarMaskMap,
totalGPURigsCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPURigsCount),
totalGPUHumanRotationDataEntriesCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPUHumanRotationDataEntriesCount),
totalGPURigBonesCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPURigBonesCount),
totalGPUAnimationClipsDataSize = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPUAnimationClipsSize),
totalGPUBoneRemapIndicesCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPUSkinnedMeshBonesCount),
totalGPUBoneRemapTablesCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPUSkinnedMeshesCount),
totalGPUAvatarMasksDataCount = (uint2*)UnsafeUtility.AddressOf(ref rad.totalGPUAvatarMasksDataCount),
};
var rv = resetFrameDataJob.Schedule(dependsOn);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle CreateNewRigsData(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
// Cannot process hash map in a parallel manner, so copy data to the list
var copyNewRigsToListJob = new CreateNewRigsListJob()
{
outList = rad.newRigsList,
newBlobAssetsMap = newRigsMap
};
var copyNewRigsToListJH = copyNewRigsToListJob.Schedule(dependsOn);
// Calculate new data capacity. Increased capacity is needed for GPU buffer resize
var calculateNewRigsOffsetsJob = new CalculateNewRigsOffsets()
{
newRigs = rad.newRigsList,
totalGPURigsCount = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPURigsCount.x)),
totalGPURigBonesCount = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPURigBonesCount.x)),
totalGPUHumanRotationDataEntriesCount = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPUHumanRotationDataEntriesCount.x))
};
var calculateNewRigsOffsetsJH = calculateNewRigsOffsetsJob.Schedule(rad.newRigsList, 1, copyNewRigsToListJH);
return calculateNewRigsOffsetsJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle CreateNewAvatarMasksData(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
var copyNewAvatarMasksToListJob = new CreateNewAvatarMasksListJob()
{
outList = rad.newAvatarMasksList,
newBlobAssetsMap = newAvatarMaskMap
};
var copyNewAvatarMasksToListJH = copyNewAvatarMasksToListJob.Schedule(dependsOn);
// Calculate new data capacity. Increased capacity is needed for GPU buffer resize
var calculateNewAvatarMaskOffsetsJob = new CalculateNewAvatarMasksOffsetsJob()
{
newAvatarMasks = rad.newAvatarMasksList,
totalAvatarMasksCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPUAvatarMasksDataCount.x)),
};
var calculateNewAvatarMaskOffsetsJH = calculateNewAvatarMaskOffsetsJob.Schedule(rad.newAvatarMasksList, 1, copyNewAvatarMasksToListJH);
return calculateNewAvatarMaskOffsetsJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle CreateNewAnimationsData(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
// Cannot process hash map in a parallel manner, so copy data to the list
var copyNewAnimationsToListJob = new CreateNewAnimationsListJob()
{
outList = rad.newAnimationsList,
newBlobAssetsMap = newAnimationsMap
};
var copyNewAnimationsToListJH = copyNewAnimationsToListJob.Schedule(dependsOn);
// Calculate new data capacity. Increased capacity is needed for GPU buffer resize
var calculateNewAnimationsOffsetsJob = new CalculateNewAnimationOffsets()
{
newAnimationClips = rad.newAnimationsList,
totalAnimationClipsOffsetCounter = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPUAnimationClipsSize.x)),
};
var calculateNewAnimationsOffsetsJH = calculateNewAnimationsOffsetsJob.Schedule(rad.newAnimationsList, 1, copyNewAnimationsToListJH);
return calculateNewAnimationsOffsetsJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle GatherNewRigsAndAnimations(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
var newFrameResourcesJob = new GatherNewRigsAndAnimationsJob()
{
existingAnimations = rad.animationClipsMap,
existingRigs = rad.rigDefinitionsMap,
existingAvatarMasks = rad.avatarMasksDataMap,
newFrameAnimations = newAnimationsMap.AsParallelWriter(),
newRigDefinitions = newRigsMap.AsParallelWriter(),
newFrameAvatarMasks = newAvatarMaskMap.AsParallelWriter()
};
var newFrameResourcesJH = newFrameResourcesJob.ScheduleParallel(dependsOn);
var createNewAnimationsJH = CreateNewAnimationsData(ref ss, ref rad, newFrameResourcesJH);
var createNewAvatarMasksJH = CreateNewAvatarMasksData(ref ss, ref rad, newFrameResourcesJH);
var createNewRigsJH = CreateNewRigsData(ref ss, ref rad, newFrameResourcesJH);
var rv = JobHandle.CombineDependencies(createNewAnimationsJH, createNewAvatarMasksJH, createNewRigsJH);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle RegisterNewResources(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
var registerNewResourcesJob = new RegisterNewResourcesJob()
{
newAnimations = rad.newAnimationsList,
animationClipsOffsets = rad.animationClipsMap,
newRigDefs = rad.newRigsList,
rigDefinitionOffsets = rad.rigDefinitionsMap,
newSkinnedMeshesDatas = rad.newSkinnedMeshesDataList,
skinnedMeshDataOffsets = rad.skinnedMeshesDataMap,
newAvatarMasks = rad.newAvatarMasksList,
avatarMasksDataOffsets = rad.avatarMasksDataMap,
maximumKeyFrameArrayLength = (uint*)UnsafeUtility.AddressOf(ref rad.maxTrackKeyframesCount)
};
var registerNewResourcesJH = registerNewResourcesJob.Schedule(dependsOn);
return registerNewResourcesJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle GatherNewRigRemapTables(ref SystemState ss, ref GPURuntimeAnimationData rad, JobHandle dependsOn)
{
var newBoneRemapTablesJob = new NewFrameRigToSkinnedMeshRemapTablesJob()
{
skinnedMeshesDataMap = rad.skinnedMeshesDataMap,
rigDefComponentLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true),
gpuAnimationEngineComponentLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true),
newFrameSkinnedMeshData = newSkinnedMeshesDataMap.AsParallelWriter()
};
var newBoneRemapTablesJH = newBoneRemapTablesJob.ScheduleParallel(dependsOn);
var copyNewRemapTablesToListJob = new CreateNewSkinnedMeshesDataListJob()
{
outList = rad.newSkinnedMeshesDataList,
newSkinnedMeshesDataMap = newSkinnedMeshesDataMap
};
var copyNewRemapTablesToListJH = copyNewRemapTablesToListJob.Schedule(newBoneRemapTablesJH);
var calculateNewBoneRemapTablesOffsetsJob = new CalculateNewBoneRemapTablesOffsetsJob()
{
newBoneRemapTables = rad.newSkinnedMeshesDataList,
totalGPUBoneRemapIndicesCount = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPUSkinnedMeshBonesCount)),
totalGPUBoneRemapTablesCount = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref rad.totalGPUSkinnedMeshesCount)),
};
var calculateNewBoneRemapTablesOffsetsJH = calculateNewBoneRemapTablesOffsetsJob.Schedule(rad.newSkinnedMeshesDataList, 1, copyNewRemapTablesToListJH);
return calculateNewBoneRemapTablesOffsetsJH;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c706bbc6cab420d45ad653e7118bfdfd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationPreparationSystem.cs
uploadId: 897522
@@ -0,0 +1,456 @@
using System;
using System.Collections.Generic;
using Unity.Assertions;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using Rukhanka.Toolbox;
using Unity.Burst;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial struct GPUAnimationPreparationSystem
{
[BurstCompile]
unsafe struct ResetFrameDataJob: IJob
{
public NativeParallelHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> newAnimationsMap;
public NativeParallelHashMap<Hash128, BlobAssetReference<RigDefinitionBlob>> newRigsMap;
public NativeParallelHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> newAvatarMasksMap;
public NativeList<GPURigDefinitionPlacementData> newRigsList;
public NativeList<GPUAnimationClipPlacementData> newAnimationsList;
public NativeList<GPUAvatarMaskPlacementData> newAvatarMasksList;
public NativeParallelHashMap<Hash128, GPUSkinnedMeshPlacementData> newSkinnedMeshDataMap;
public NativeList<GPUSkinnedMeshPlacementData> newSkinnedMeshDataList;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPUAnimationClipsDataSize;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPURigsCount;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPURigBonesCount;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPUBoneRemapIndicesCount;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPUBoneRemapTablesCount;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPUHumanRotationDataEntriesCount;
[NativeDisableUnsafePtrRestriction]
public uint2 *totalGPUAvatarMasksDataCount;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
newAnimationsMap.Clear();
newRigsMap.Clear();
newAvatarMasksMap.Clear();
newRigsList.Clear();
newAnimationsList.Clear();
newAvatarMasksList.Clear();
newSkinnedMeshDataMap.Clear();
foreach (var nbrt in newSkinnedMeshDataList)
{
nbrt.boneRemapTableBlob.Dispose();
}
newSkinnedMeshDataList.Clear();
totalGPUAnimationClipsDataSize->y = totalGPUAnimationClipsDataSize->x;
totalGPURigsCount->y = totalGPURigsCount->x;
totalGPURigBonesCount->y = totalGPURigBonesCount->x;
totalGPUBoneRemapIndicesCount->y = totalGPUBoneRemapIndicesCount->x;
totalGPUBoneRemapTablesCount->y = totalGPUBoneRemapTablesCount->x;
totalGPUHumanRotationDataEntriesCount->y = totalGPUHumanRotationDataEntriesCount->x;
totalGPUAvatarMasksDataCount->y = totalGPUAvatarMasksDataCount->x;
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
[WithAll(typeof(GPUAnimationEngineTag))]
partial struct GatherNewRigsAndAnimationsJob: IJobEntity
{
[ReadOnly]
public NativeParallelHashMap<Hash128, int> existingAnimations;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> existingRigs;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> existingAvatarMasks;
[NativeDisableContainerSafetyRestriction]
public NativeParallelHashMap<Hash128, BlobAssetReference<AnimationClipBlob>>.ParallelWriter newFrameAnimations;
[NativeDisableContainerSafetyRestriction]
public NativeParallelHashMap<Hash128, BlobAssetReference<RigDefinitionBlob>>.ParallelWriter newRigDefinitions;
[NativeDisableContainerSafetyRestriction]
public NativeParallelHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>>.ParallelWriter newFrameAvatarMasks;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in RigDefinitionComponent rdc, in DynamicBuffer<AnimationToProcessComponent> atps)
{
var rigHash = rdc.rigBlob.Value.hash;
if (!existingRigs.TryGetValue(rigHash, out _))
{
newRigDefinitions.TryAdd(rigHash, rdc.rigBlob);
}
for (var i = 0; i < atps.Length; ++i)
{
var atp = atps[i];
if (!atp.animation.IsCreated)
continue;
// Add animation
var animationHash = atp.animation.Value.hash;
if (!existingAnimations.TryGetValue(animationHash, out _))
{
newFrameAnimations.TryAdd(animationHash, atp.animation);
}
// Add avatar mask
if (atp.avatarMask.IsCreated)
{
var avatarMaskHash = atp.avatarMask.Value.hash;
if (!existingAvatarMasks.TryGetValue(avatarMaskHash, out _))
{
newFrameAvatarMasks.TryAdd(avatarMaskHash, atp.avatarMask);
}
}
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CreateNewAnimationsListJob: IJob
{
[ReadOnly]
public NativeParallelHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> newBlobAssetsMap;
public NativeList<GPUAnimationClipPlacementData> outList;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
outList.Capacity = math.max(newBlobAssetsMap.Count(), outList.Capacity);
foreach (var nba in newBlobAssetsMap)
{
var acp = new GPUAnimationClipPlacementData()
{
animationClipBlob = nba.Value
};
outList.Add(acp);
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CreateNewAvatarMasksListJob: IJob
{
[ReadOnly]
public NativeParallelHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> newBlobAssetsMap;
public NativeList<GPUAvatarMaskPlacementData> outList;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
outList.Capacity = math.max(newBlobAssetsMap.Count(), outList.Capacity);
foreach (var nba in newBlobAssetsMap)
{
var acp = new GPUAvatarMaskPlacementData()
{
avatarMaskBlob = nba.Value
};
outList.Add(acp);
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CreateNewRigsListJob: IJob
{
[ReadOnly]
public NativeParallelHashMap<Hash128, BlobAssetReference<RigDefinitionBlob>> newBlobAssetsMap;
public NativeList<GPURigDefinitionPlacementData> outList;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
outList.Capacity = math.max(newBlobAssetsMap.Count(), outList.Capacity);
foreach (var nba in newBlobAssetsMap)
{
var rdp = new GPURigDefinitionPlacementData()
{
rigBlob = nba.Value,
};
outList.Add(rdp);
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CreateNewSkinnedMeshesDataListJob: IJob
{
[ReadOnly]
public NativeParallelHashMap<Hash128, GPUSkinnedMeshPlacementData> newSkinnedMeshesDataMap;
public NativeList<GPUSkinnedMeshPlacementData> outList;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
outList.Capacity = math.max(newSkinnedMeshesDataMap.Count(), outList.Capacity);
foreach (var nba in newSkinnedMeshesDataMap)
{
outList.Add(nba.Value);
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
partial struct NewFrameRigToSkinnedMeshRemapTablesJob: IJobEntity
{
[ReadOnly]
public NativeParallelHashMap<Hash128, int> skinnedMeshesDataMap;
[ReadOnly]
public ComponentLookup<GPUAnimationEngineTag> gpuAnimationEngineComponentLookup;
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefComponentLookup;
[WriteOnly, NativeDisableContainerSafetyRestriction]
public NativeParallelHashMap<Hash128, GPUSkinnedMeshPlacementData>.ParallelWriter newFrameSkinnedMeshData;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in SkinnedMeshRendererComponent asmc)
{
if (!asmc.IsGPUAnimator(gpuAnimationEngineComponentLookup))
return;
rigDefComponentLookup.TryGetComponent(asmc.animatedRigEntity, out var rigDef);
var hash = AnimationUtils.CalculateBoneRemapTableHash(asmc.smrInfoBlob, rigDef.rigBlob);
if (skinnedMeshesDataMap.TryGetValue(hash, out _))
return;
var remapTableBlob = AnimationUtils.MakeSkinnedMeshToRigRemapTable(asmc, rigDef, Allocator.TempJob);
var skinnedMeshInfo = new GPUSkinnedMeshPlacementData()
{
hash = hash,
dataOffset = -1,
skinnedMeshInfo = asmc.smrInfoBlob,
boneRemapTableBlob = remapTableBlob
};
if (!newFrameSkinnedMeshData.TryAdd(hash, skinnedMeshInfo))
remapTableBlob.Dispose();
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CalculateNewAnimationOffsets: IJobParallelForDefer
{
[NativeDisableParallelForRestriction]
public NativeList<GPUAnimationClipPlacementData> newAnimationClips;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalAnimationClipsOffsetCounter;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(int index)
{
ref var ac = ref newAnimationClips.ElementAt(index);
ref var acb = ref ac.animationClipBlob.Value;
var clipSize = CalculateGPUAnimationClipPlacementData(ref acb, ref ac);
ac.animationClipDataOffset = totalAnimationClipsOffsetCounter.Add(clipSize);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CalculateGPUAnimationClipPlacementData(ref AnimationClipBlob acb, ref GPUAnimationClipPlacementData acpd)
{
var totalBytes = 0;
totalBytes += UnsafeUtility.SizeOf<GPUStructures.AnimationClip>();
totalBytes = CalculateGPUTrackSetSize(totalBytes, ref acb.clipTracks, ref acpd.clipTracks);
totalBytes = CalculateGPUTrackSetSize(totalBytes, ref acb.additiveReferencePoseFrame, ref acpd.additiveRefPoseTracks);
return totalBytes;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CalculateGPUTrackSetSize(int currentOffset, ref TrackSet ts, ref GPUTrackSetPlacementData tpd)
{
var rv = currentOffset;
if (ts.keyframes.Length == 0)
{
tpd = GPUTrackSetPlacementData.Empty();
return rv;
}
tpd.keyFramesOffset = rv;
rv += ts.keyframes.Length * UnsafeUtility.SizeOf<GPUStructures.KeyFrame>();
tpd.tracksOffset = rv;
rv += ts.tracks.Length * UnsafeUtility.SizeOf<GPUStructures.Track>();
tpd.trackGroupsOffset = rv;
rv += ts.trackGroups.Length * UnsafeUtility.SizeOf<int>();
tpd.hashTableOffset = rv;
rv += ts.trackGroupPHT.pht.Length * UnsafeUtility.SizeOf<int2>();
return rv;
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CalculateNewAvatarMasksOffsetsJob: IJobParallelForDefer
{
[NativeDisableParallelForRestriction]
public NativeList<GPUAvatarMaskPlacementData> newAvatarMasks;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalAvatarMasksCounter;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(int index)
{
ref var am = ref newAvatarMasks.ElementAt(index);
ref var amb = ref am.avatarMaskBlob.Value;
// Plus one because we need space for human body parts mask
am.dataOffset = totalAvatarMasksCounter.Add(amb.includedBoneMask.Length + 1);
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CalculateNewRigsOffsets: IJobParallelForDefer
{
[NativeDisableParallelForRestriction]
public NativeList<GPURigDefinitionPlacementData> newRigs;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalGPURigsCount;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalGPURigBonesCount;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalGPUHumanRotationDataEntriesCount;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(int index)
{
ref var rig = ref newRigs.ElementAt(index);
rig.rigDefinitionIndex = totalGPURigsCount.Add(1);
ref var rb = ref rig.rigBlob.Value;
rig.rigBonesOffset = totalGPURigBonesCount.Add(rb.bones.Length);
rig.humanRotationDataOffset = -1;
if (rb.humanData.IsValid)
rig.humanRotationDataOffset = totalGPUHumanRotationDataEntriesCount.Add(rb.humanData.Value.humanRotData.Length);
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CalculateNewBoneRemapTablesOffsetsJob: IJobParallelForDefer
{
[NativeDisableParallelForRestriction]
public NativeList<GPUSkinnedMeshPlacementData> newBoneRemapTables;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalGPUBoneRemapTablesCount;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 totalGPUBoneRemapIndicesCount;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(int index)
{
ref var rig = ref newBoneRemapTables.ElementAt(index);
rig.dataOffset = totalGPUBoneRemapIndicesCount.Add(rig.boneRemapTableBlob.Value.remapIndices.Length);
totalGPUBoneRemapTablesCount.Add(1);
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
unsafe struct RegisterNewResourcesJob: IJob
{
[ReadOnly]
public NativeList<GPUAnimationClipPlacementData> newAnimations;
[ReadOnly]
public NativeList<GPURigDefinitionPlacementData> newRigDefs;
[ReadOnly]
public NativeList<GPUSkinnedMeshPlacementData> newSkinnedMeshesDatas;
[ReadOnly]
public NativeList<GPUAvatarMaskPlacementData> newAvatarMasks;
public NativeParallelHashMap<Hash128, int> animationClipsOffsets;
public NativeParallelHashMap<Hash128, int> rigDefinitionOffsets;
public NativeParallelHashMap<Hash128, int> skinnedMeshDataOffsets;
public NativeParallelHashMap<Hash128, int> avatarMasksDataOffsets;
[NativeDisableUnsafePtrRestriction]
public uint *maximumKeyFrameArrayLength;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
foreach (var a in newAnimations)
{
animationClipsOffsets.Add(a.animationClipBlob.Value.hash, a.animationClipDataOffset);
*maximumKeyFrameArrayLength = math.max(*maximumKeyFrameArrayLength, a.animationClipBlob.Value.maxTrackKeyframeLength);
}
foreach (var a in newRigDefs)
{
rigDefinitionOffsets.Add(a.rigBlob.Value.hash, a.rigDefinitionIndex);
}
foreach (var a in newSkinnedMeshesDatas)
{
skinnedMeshDataOffsets.Add(a.hash, a.dataOffset);
}
foreach (var a in newAvatarMasks)
{
avatarMasksDataOffsets.Add(a.avatarMaskBlob.Value.hash, a.dataOffset);
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 936431d056ec6a84a9b63f42a8f8e087
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationPreparationSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,580 @@
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
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ed0a503b97e8daf44baa4bcda24bfc15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationSystem.cs
uploadId: 897522
@@ -0,0 +1,569 @@
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<GPUAnimationClipPlacementData> 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<GPUStructures.KeyFrame>(), baseOffset + tspd.keyFramesOffset);
gpuAnimationClips.AddUpload(ts.trackGroups.GetUnsafePtr(), ts.trackGroups.Length * UnsafeUtility.SizeOf<int>(), baseOffset + tspd.trackGroupsOffset);
gpuAnimationClips.AddUpload(ts.trackGroupPHT.pht.GetUnsafePtr(), ts.trackGroupPHT.pht.Length * UnsafeUtility.SizeOf<int2>(), baseOffset + tspd.hashTableOffset);
#if RUKHANKA_DEBUG_INFO
// Must copy because debug tracks contain name field
var tmpArr = new NativeArray<GPUStructures.Track>(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<GPUStructures.Track>(), baseOffset + tspd.tracksOffset);
#endif
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CopyNewAvatarMasksToGPUJob: IJobFor
{
[ReadOnly]
public NativeList<GPUAvatarMaskPlacementData> newAvatarMasks;
public ThreadedSparseUploader gpuAvatarMasks;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe void Execute(int index)
{
var am = newAvatarMasks[index];
var dstOffset = am.dataOffset * UnsafeUtility.SizeOf<uint>();
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<uint>();
gpuAvatarMasks.AddUpload(srcPtr, srcSize, dstOffset + 4);
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CopyNewRigsToGPUJob: IJobFor
{
[ReadOnly]
public NativeList<GPURigDefinitionPlacementData> 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<GPUStructures.RigBone>();
gpuRigBones.AddUpload(rb, dstRigBoneOffsetInBytes);
}
#else
var srcPtr = rdb.bones.GetUnsafePtr();
var srcOffset = rig.rigBonesOffset * UnsafeUtility.SizeOf<GPUStructures.RigBone>();
var srcSize = rdb.bones.Length * UnsafeUtility.SizeOf<GPUStructures.RigBone>();
gpuRigBones.AddUpload(srcPtr, srcSize, srcOffset);
#endif
if (rig.humanRotationDataOffset >= 0)
{
BurstAssert.IsTrue(UnsafeUtility.SizeOf<GPUStructures.HumanRotationData>() == UnsafeUtility.SizeOf<HumanRotationData>(), "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<GPUStructures.HumanRotationData>();
var srcHRDSize = rdb.humanData.Value.humanRotData.Length * UnsafeUtility.SizeOf<GPUStructures.HumanRotationData>();
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<GPUStructures.RigDefinition>();
gpuRigDefs.AddUpload(gpuRig, dstRigDefOffsetInBytes);
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct CopyNewSkinnedMeshesDataJob: IJobFor
{
[ReadOnly]
public NativeList<GPUSkinnedMeshPlacementData> 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<GPUStructures.SkinnedMeshBoneData>();
gpuSkinnedMeshBonesData.AddUpload(smbd, dstOffsetInBytes);
}
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
struct FillFrameAnimatedRigWorkloadBuffersJob: IJobChunk
{
[NativeDisableParallelForRestriction]
public NativeArray<GPUStructures.AnimatedBoneWorkload> animatedBonesWorkloadBuf;
[NativeDisableParallelForRestriction]
public NativeArray<GPUStructures.AnimationJob> frameRigAnimationJobs;
[NativeDisableParallelForRestriction]
public NativeArray<GPUStructures.AnimationToProcess> animationToProcessBuf;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> animationClipsOffsets;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> avatarMasksOffsets;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> rigDefinitionOffsets;
[ReadOnly]
public ComponentTypeHandle<RigDefinitionComponent> rigDefComponentTypeHandle;
[ReadOnly]
public BufferTypeHandle<AnimationToProcessComponent> atpBufTypeHandle;
[ReadOnly]
public EntityTypeHandle entityTypeHandle;
[ReadOnly]
public ComponentTypeHandle<GPURigFrameOffsetsComponent> 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<AnimationToProcessComponent> 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<GPUAnimationEngineTag> gpuAnimationEngineTagLookup;
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<GPURigFrameOffsetsComponent> frameOffsetsLookup;
[ReadOnly]
public ComponentLookup<LocalToWorld> localToWorldLookup;
[ReadOnly]
public NativeParallelHashMap<Hash128, int> skinnedMeshDataMap;
[ReadOnly]
public NativeParallelHashMap<Entity, SkinnedMeshRendererFrameDeformationData> entityToSMRFrameDataMap;
[NativeDisableParallelForRestriction]
public NativeArray<GPUStructures.SkinnedMeshWorkload> frameSkinMatrixWorkloadBuf;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 frameSkinnedMeshesAtomicCounter;
[ReadOnly]
public ComponentTypeHandle<SkinnedMeshRendererComponent> smrTypeHandle;
[ReadOnly]
public ComponentTypeHandle<LocalToWorld> l2wTypeHandle;
[ReadOnly]
public EntityTypeHandle entityTypeHandle;
[ReadOnly]
public ComponentTypeHandle<GPURigFrameOffsetsComponent> 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<RigDefinitionComponent> rigDefComponentTypeHandle;
[ReadOnly]
public BufferTypeHandle<AnimationToProcessComponent> atpBufTypeHandle;
public ComponentTypeHandle<GPURigFrameOffsetsComponent> 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<GPURigFrameOffsetsComponent> 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<GPUAnimationEngineTag> gpuAnimationEngineTagLookup;
[NativeDisableParallelForRestriction]
public NativeList<uint> frameSkinnedMeshesPerThreadCounters;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in SkinnedMeshRendererComponent asmc)
{
if (!asmc.IsGPUAnimator(gpuAnimationEngineTagLookup))
return;
frameSkinnedMeshesPerThreadCounters[JobsUtility.ThreadIndex] += 1;
}
}
//-----------------------------------------------------------------------------------------------------------------//
[BurstCompile]
unsafe struct ComputeFrameSkinnedMeshWorkloadSizesTotalJob: IJob
{
[ReadOnly]
public NativeList<uint> 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<uint> frameSkinnedMeshesPerThreadCounters;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
frameAnimatedBonesCounter.Reset(0);
frameAnimatedRigsCounter.Reset(0);
frameSkinnedMeshesCounter.Reset(0);
frameAnimationToProcessCounter.Reset(0);
frameSkinnedMeshesPerThreadCounters.Clear();
frameSkinnedMeshesPerThreadCounters.Resize(JobsUtility.ThreadIndexCount, NativeArrayOptions.ClearMemory);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ff409b692d92c6c42961eb3c0f78059a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,28 @@
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial class GPUAnimationSystem
{
readonly int ShaderID_outAnimatedBones = Shader.PropertyToID("outAnimatedBones");
readonly int ShaderID_animatedBoneWorkload = Shader.PropertyToID("animatedBoneWorkload");
readonly int ShaderID_animationJobs = Shader.PropertyToID("animationJobs");
readonly int ShaderID_animationsToProcess = Shader.PropertyToID("animationsToProcess");
readonly int ShaderID_rigDefinitions = Shader.PropertyToID("rigDefinitions");
readonly int ShaderID_rigBones = Shader.PropertyToID("rigBones");
readonly int ShaderID_animationClips = Shader.PropertyToID("animationClips");
readonly int ShaderID_humanRotationDataBuffer = Shader.PropertyToID("humanRotationDataBuffer");
readonly int ShaderID_animatedBonesCount = Shader.PropertyToID("animatedBonesCount");
readonly int ShaderID_outBoneTransforms = Shader.PropertyToID("outBoneTransforms");
readonly int ShaderID_avatarMasksBuffer = Shader.PropertyToID("avatarMasksBuffer");
readonly int ShaderID_skinMatrixWorkloadBuf = Shader.PropertyToID("skinMatrixWorkloadBuf");
readonly int ShaderID_outSkinMatrices = Shader.PropertyToID("outSkinMatrices");
readonly int ShaderID_skinnedMeshBoneData = Shader.PropertyToID("skinnedMeshBoneData");
readonly int ShaderID_totalSkinnedMeshes = Shader.PropertyToID("totalSkinnedMeshes");
public static readonly int ShaderID_boneLocalTransforms = Shader.PropertyToID("boneLocalTransforms");
public static readonly int ShaderID_rigSpaceBoneTransformsBuf = Shader.PropertyToID("rigSpaceBoneTransformsBuf");
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2b6a8234a3200384f94471df88fb843b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAnimationSystem_ShaderVars.cs
uploadId: 897522
@@ -0,0 +1,64 @@
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[WorldSystemFilter(WorldSystemFilterFlags.Editor | WorldSystemFilterFlags.Default)]
[UpdateInGroup(typeof(RukhankaDeformationSystemGroup))]
[UpdateAfter(typeof(MeshDeformationSystem))]
partial class GPUAttachmentsUpdateSystem: SystemBase
{
GraphicsBuffer dummyGB;
EntityQuery gpuAttachmentsQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected override void OnCreate()
{
gpuAttachmentsQuery = SystemAPI.QueryBuilder()
.WithAll<GPUAttachmentBoneIndexMPComponent, GPUAttachmentComponent>()
.Build();
RequireForUpdate(gpuAttachmentsQuery);
// Make small dummy bone transform buffer to prevent "attempted to draw with missing bindings" warnings and missed meshes for GPU attachments in edit mode
dummyGB = new (GraphicsBuffer.Target.Structured, GraphicsBuffer.UsageFlags.None, 1, 4);
Shader.SetGlobalBuffer(GPUAnimationSystem.ShaderID_rigSpaceBoneTransformsBuf, dummyGB);
Shader.SetGlobalBuffer(GPUAnimationSystem.ShaderID_boneLocalTransforms, dummyGB);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected override void OnDestroy()
{
dummyGB.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
protected override void OnUpdate()
{
var attachmentBoneIndexUpdateJob = new UpdateGPUAttachmentBoneIndexJob()
{
frameOffsetsLookup = SystemAPI.GetComponentLookup<GPURigFrameOffsetsComponent>(true),
localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true),
parentLookup = SystemAPI.GetComponentLookup<Parent>(true),
postTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
rigBoneRefLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true),
gpuAttachmentTypeHandle = SystemAPI.GetComponentTypeHandle<GPUAttachmentComponent>(true),
gpuAttachmentBoneIndexTypeHandle = SystemAPI.GetComponentTypeHandle<GPUAttachmentBoneIndexMPComponent>(),
gpuAttachmentToBoneTransformTypeHandle = SystemAPI.GetComponentTypeHandle<GPUAttachmentToBoneTransformMPComponent>(),
gpuRigEntityLocalToWorldTypeHandle = SystemAPI.GetComponentTypeHandle<GPURigEntityLocalToWorldMPComponent>(),
gpuRigFrameOffsetsTypeHandle = SystemAPI.GetComponentTypeHandle<GPURigFrameOffsetsComponent>(true),
gpuEngineTagLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true),
entityTypeHandle = SystemAPI.GetEntityTypeHandle()
};
var attachmentBoneIndexUpdateJH = attachmentBoneIndexUpdateJob.ScheduleParallel(gpuAttachmentsQuery, Dependency);
Dependency = attachmentBoneIndexUpdateJH;
Dependency.Complete();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0dce0f4078c7add4fa8632fffc9dc9e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAttachementsUpdateSystem.cs
uploadId: 897522
@@ -0,0 +1,149 @@
using Rukhanka.Toolbox;
using Unity.Entities;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Transforms;
using TransformHelpers = Unity.Transforms.TransformHelpers;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
partial class GPUAttachmentsUpdateSystem
{
[BurstCompile]
struct UpdateGPUAttachmentBoneIndexJob: IJobChunk
{
struct ParentBoneInfo
{
public Entity boneEntity;
public Entity rigEntity;
public int boneIndexInRig;
public float4x4 attachmentToBoneTransform;
}
[ReadOnly]
public ComponentLookup<GPURigFrameOffsetsComponent> frameOffsetsLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<PostTransformMatrix> postTransformMatrixLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> rigBoneRefLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[ReadOnly]
public ComponentTypeHandle<GPUAttachmentComponent> gpuAttachmentTypeHandle;
[ReadOnly]
public ComponentTypeHandle<GPURigFrameOffsetsComponent> gpuRigFrameOffsetsTypeHandle;
[ReadOnly]
public ComponentLookup<GPUAnimationEngineTag> gpuEngineTagLookup;
[ReadOnly]
public EntityTypeHandle entityTypeHandle;
public ComponentTypeHandle<GPUAttachmentBoneIndexMPComponent> gpuAttachmentBoneIndexTypeHandle;
public ComponentTypeHandle<GPUAttachmentToBoneTransformMPComponent> gpuAttachmentToBoneTransformTypeHandle;
public ComponentTypeHandle<GPURigEntityLocalToWorldMPComponent> gpuRigEntityLocalToWorldTypeHandle;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var gpuAttachments = chunk.GetNativeArray(ref gpuAttachmentTypeHandle);
var gpuAttachmentBoneIndices = chunk.GetNativeArray(ref gpuAttachmentBoneIndexTypeHandle);
var gpuAttachmentToBoneTransforms = chunk.GetNativeArray(ref gpuAttachmentToBoneTransformTypeHandle);
var gpuRigEntityLocalToWorlds = chunk.GetNativeArray(ref gpuRigEntityLocalToWorldTypeHandle);
var entities = chunk.GetNativeArray(entityTypeHandle);
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
while (cee.NextEntityIndex(out var i))
{
var gpuAttachment = gpuAttachments[i];
var gpuAttachmentBoneIndex = gpuAttachmentBoneIndices[i];
var gpuAttachmentToBoneTransform = gpuAttachmentToBoneTransforms[i];
var gpuRigEntityToL2W = gpuRigEntityLocalToWorlds[i];
var e = entities[i];
Execute(e, ref gpuAttachmentBoneIndex, ref gpuAttachmentToBoneTransform, ref gpuRigEntityToL2W, gpuAttachment, chunk);
gpuAttachmentBoneIndices[i] = gpuAttachmentBoneIndex;
gpuAttachmentToBoneTransforms[i] = gpuAttachmentToBoneTransform;
gpuRigEntityLocalToWorlds[i] = gpuRigEntityToL2W;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute
(
Entity e,
ref GPUAttachmentBoneIndexMPComponent abi,
ref GPUAttachmentToBoneTransformMPComponent atbt,
ref GPURigEntityLocalToWorldMPComponent rltw,
in GPUAttachmentComponent ac,
in ArchetypeChunk chunk
)
{
abi.boneIndex = -1;
var pbi = GetParentRigBoneEntity(e, ac);
if (pbi.boneEntity == Entity.Null)
return;
// Act as ordinary attachment in case of CPU animation engine
if (!AnimationUtils.IsGPUAnimator(pbi.rigEntity, gpuEngineTagLookup))
return;
atbt.value = pbi.attachmentToBoneTransform;
TransformHelpers.ComputeWorldTransformMatrix(pbi.rigEntity, out rltw.value, ref localTransformLookup, ref parentLookup, ref postTransformMatrixLookup);
if (frameOffsetsLookup.TryGetComponent(pbi.rigEntity, out var boneOffset) &&
EntityTools.TryGetChunkComponentData(chunk, pbi.rigEntity, ref gpuRigFrameOffsetsTypeHandle, out var chunkFrameOffsets))
{
boneOffset.AddOffsets(chunkFrameOffsets);
abi.boneIndex = boneOffset.boneIndex + pbi.boneIndexInRig;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParentBoneInfo GetParentRigBoneEntity(Entity e, GPUAttachmentComponent ac)
{
AnimatorEntityRefComponent rbr = default;
float4x4 entityToBoneTransform = float4x4.identity;
var parent = new Parent() { Value = e};
do
{
if (!localTransformLookup.TryGetComponent(parent.Value, out var plt))
break;
if (rigBoneRefLookup.TryGetComponent(parent.Value, out rbr))
break;
var pltMat = plt.ToMatrix();
if (Hint.Unlikely(postTransformMatrixLookup.TryGetComponent(parent.Value, out var postTransformMatrixComponent)))
{
pltMat = math.mul(pltMat, postTransformMatrixComponent.Value);
}
entityToBoneTransform = math.mul(pltMat, entityToBoneTransform);
}
while (parentLookup.TryGetComponent(parent.Value, out parent));
var rv = new ParentBoneInfo();
if (rbr.animatorEntity != Entity.Null)
{
rv.boneEntity = parent.Value;
rv.rigEntity = rbr.animatorEntity;
rv.boneIndexInRig = math.select(rbr.boneIndexInAnimationRig, ac.attachedBoneIndex, ac.attachedBoneIndex >= 0);
rv.attachmentToBoneTransform = entityToBoneTransform;
}
return rv;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c162722eee64c5047bbb4a2d7c06ae69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUAttachementsUpdateSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,162 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct GPUTrackSetPlacementData
{
public int keyFramesOffset;
public int tracksOffset;
public int trackGroupsOffset;
public int hashTableOffset;
internal GPUStructures.TrackSet ToGPUTrackSet()
{
var rv = new GPUStructures.TrackSet()
{
tracksOffset = tracksOffset,
keyFramesOffset = keyFramesOffset,
trackGroupsOffset = trackGroupsOffset,
trackGroupPHTOffset = hashTableOffset,
};
return rv;
}
internal static GPUTrackSetPlacementData Empty()
{
var rv = new GPUTrackSetPlacementData()
{
tracksOffset = -1,
keyFramesOffset = -1,
trackGroupsOffset = -1,
hashTableOffset = -1,
};
return rv;
}
}
//-----------------------------------------------------------------------------------------------------------------//
public struct GPUAnimationClipPlacementData
{
public BlobAssetReference<AnimationClipBlob> animationClipBlob;
public int animationClipDataOffset;
public GPUTrackSetPlacementData clipTracks;
public GPUTrackSetPlacementData additiveRefPoseTracks;
}
//-----------------------------------------------------------------------------------------------------------------//
public struct GPURigDefinitionPlacementData
{
public BlobAssetReference<RigDefinitionBlob> rigBlob;
public int rigDefinitionIndex;
public int rigBonesOffset;
public int humanRotationDataOffset;
}
//-----------------------------------------------------------------------------------------------------------------//
public struct GPUSkinnedMeshPlacementData
{
public BlobAssetReference<BoneRemapTableBlob> boneRemapTableBlob;
public BlobAssetReference<SkinnedMeshInfoBlob> skinnedMeshInfo;
public Hash128 hash;
public int dataOffset;
}
//-----------------------------------------------------------------------------------------------------------------//
public struct GPUAvatarMaskPlacementData
{
public BlobAssetReference<AvatarMaskBlob> avatarMaskBlob;
public int dataOffset;
}
//-----------------------------------------------------------------------------------------------------------------//
public struct GPURuntimeAnimationData: IComponentData, IDisposable
{
public NativeList<GPUAnimationClipPlacementData> newAnimationsList;
public NativeList<GPURigDefinitionPlacementData> newRigsList;
public NativeList<GPUSkinnedMeshPlacementData> newSkinnedMeshesDataList;
public NativeList<GPUAvatarMaskPlacementData> newAvatarMasksList;
public NativeParallelHashMap<Hash128, int> animationClipsMap;
public uint2 totalGPUAnimationClipsSize;
public uint maxTrackKeyframesCount;
public NativeParallelHashMap<Hash128, int> rigDefinitionsMap;
public uint2 totalGPURigsCount;
public uint2 totalGPUHumanRotationDataEntriesCount;
public uint2 totalGPURigBonesCount;
public NativeParallelHashMap<Hash128, int> skinnedMeshesDataMap;
public uint2 totalGPUSkinnedMeshBonesCount;
public uint2 totalGPUSkinnedMeshesCount;
public NativeParallelHashMap<Hash128, int> avatarMasksDataMap;
public uint2 totalGPUAvatarMasksDataCount;
public uint frameAnimatedBonesCounter;
public uint frameAnimatedRigsCounter;
public uint frameAnimationToProcessCounter;
public NativeList<uint> frameSkinnedMeshesPerThreadCounters;
public uint frameSkinnedMeshesCounter;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static GPURuntimeAnimationData Construct()
{
var initialCapacity = 0x1000;
var rv = new GPURuntimeAnimationData();
rv.newAnimationsList = new (initialCapacity, Allocator.Persistent);
rv.newRigsList = new (initialCapacity, Allocator.Persistent);
rv.newSkinnedMeshesDataList = new (initialCapacity, Allocator.Persistent);
rv.newAvatarMasksList = new (initialCapacity, Allocator.Persistent);
rv.animationClipsMap = new (initialCapacity, Allocator.Persistent);
rv.rigDefinitionsMap = new (initialCapacity, Allocator.Persistent);
rv.skinnedMeshesDataMap = new (initialCapacity, Allocator.Persistent);
rv.avatarMasksDataMap = new (initialCapacity, Allocator.Persistent);
rv.totalGPUAnimationClipsSize = 0;
rv.totalGPURigsCount = 0;
rv.totalGPUHumanRotationDataEntriesCount = 0;
rv.totalGPURigBonesCount = 0;
rv.totalGPUSkinnedMeshBonesCount = 0;
rv.totalGPUSkinnedMeshesCount = 0;
rv.totalGPUAvatarMasksDataCount = 0;
rv.maxTrackKeyframesCount = 0;
rv.frameAnimatedBonesCounter = 0;
rv.frameAnimatedRigsCounter = 0;
rv.frameAnimationToProcessCounter = 0;
rv.frameSkinnedMeshesPerThreadCounters = new (JobsUtility.MaxJobThreadCount, Allocator.Persistent);
rv.frameSkinnedMeshesCounter = 0;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
newAnimationsList.Dispose();
newRigsList.Dispose();
newSkinnedMeshesDataList.Dispose();
newAvatarMasksList.Dispose();
animationClipsMap.Dispose();
rigDefinitionsMap.Dispose();
skinnedMeshesDataMap.Dispose();
frameSkinnedMeshesPerThreadCounters.Dispose();
avatarMasksDataMap.Dispose();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 637b05431b241324098e50f624437bcb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPURuntimeAnimationData.cs
uploadId: 897522
@@ -0,0 +1,184 @@
using Unity.Collections;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.GPUStructures
{
internal struct KeyFrame
{
public float v;
public float inTan, outTan;
public float time;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct Track
{
public uint props;
public int2 keyFrameRange;
public uint bindingType
{
get => props & 0xf;
set => props = value | props & 0xfffffff0;
}
public uint channelIndex
{
get => props >> 4 & 3;
set => props = value << 4 | props & 0xffffffcf;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct TrackSet
{
public int keyFramesOffset;
public int tracksOffset;
public int trackGroupsOffset;
public int trackGroupPHTOffset;
public uint trackGroupPHTSeed;
public uint trackGroupPHTSizeMask;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct AnimationClip
{
public uint4 hash;
public TrackSet clipTracks;
public TrackSet additiveReferencePoseTracks;
public uint flags;
public float cycleOffset;
public float length;
public bool looped
{
get => new BitField32(flags).IsSet(1);
set
{
var k = new BitField32(flags);
k.SetBits(1, value);
flags = k.Value;
}
}
public bool loopPoseBlend
{
get => new BitField32(flags).IsSet(2);
set
{
var k = new BitField32(flags);
k.SetBits(2, value);
flags = k.Value;
}
}
public bool hasRootMotionCurves
{
get => new BitField32(flags).IsSet(3);
set
{
var k = new BitField32(flags);
k.SetBits(3, value);
flags = k.Value;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct BoneTransform
{
public float3 pos;
public float4 rot;
public float3 scale;
public BoneTransform(Rukhanka.BoneTransform bt)
{
pos = bt.pos;
rot = bt.rot.value;
scale = bt.scale;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct RigBone
{
public uint hash;
public int parentBoneIndex;
public BoneTransform refPose;
public int humanBodyPart;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct RigDefinition
{
public uint4 hash;
public int2 rigBonesRange;
public int rootBoneIndex;
public int humanRotationDataOffset;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct AnimationJob
{
public int rigDefinitionIndex;
public int animatedBoneIndexOffset;
public int2 animationsToProcessRange;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct AnimationToProcess
{
public int animationClipAddress;
public float weight;
public float time;
public int blendMode;
public float layerWeight;
public int layerIndex;
public int avatarMaskDataOffset;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct AnimatedBoneWorkload
{
public int boneIndexInRig;
public int animationJobIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct SkinnedMeshWorkload
{
public int skinMatrixBaseOutIndex;
public int boneRemapTableIndex;
public int skinMatricesCount;
public int rootBoneIndex;
public int animatedBoneIndexOffset;
public float4x4 skinnedMeshInverseTransform;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct SkinnedMeshBoneData
{
public int boneRemapIndex;
public float3x4 bindPose;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct HumanRotationData
{
public float3 minMuscleAngles, maxMuscleAngles;
public float4 preRot, postRot;
public float3 sign;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b91a58e950e22e9418767c351497b667
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/GPUStructures.cs
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e8c4107a6c99fbf4198757fac93c97cc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
#pragma kernel ProcessAnimations
#pragma kernel MakeRigSpaceBoneTransforms
#pragma kernel ComputeSkinMatrices
//#pragma enable_d3d11_debug_symbols
//#pragma use_dxc
/////////////////////////////////////////////////////////////////////////////////
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/ShaderConf.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/Debug.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/GPUStructures.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/MakeSkinMatrices.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/ProcessAnimations.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/MakeRigSpaceBoneTransforms.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/MakeSkinMatrices.hlsl"
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 7fb582dc2671eaa40869fc5ceea5f2c4
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/GPUAnimationEngine.compute
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fb0379f8129846944bf9d56dca4b60fc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,66 @@
#pragma once
//#pragma enable_d3d11_debug_symbols
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/GPUStructures.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/ShaderConf.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/Debug.hlsl"
/////////////////////////////////////////////////////////////////////////////////
#if defined(DOTS_INSTANCING_ON)
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
UNITY_DOTS_INSTANCED_PROP(int, _RukhankaGPUBoneIndex)
UNITY_DOTS_INSTANCED_PROP(float4x4, _RukhankaAttachmentToBoneTransform)
UNITY_DOTS_INSTANCED_PROP(float4x4, _RukhankaAnimatedEntityLocalToWorld)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
StructuredBuffer<BoneTransform> boneLocalTransforms;
StructuredBuffer<BoneTransform> rigSpaceBoneTransformsBuf;
#endif
/////////////////////////////////////////////////////////////////////////////////
void GPUAttachmentMeshMover_float(in float3 vertex, in float3 normal, in float3 tangent, out float3 animatedVertex, out float3 animatedNormal, out float3 animatedTangent)
{
#if defined(DOTS_INSTANCING_ON)
int gpuBoneIndex = UNITY_ACCESS_DOTS_INSTANCED_PROP(int, _RukhankaGPUBoneIndex);
if (gpuBoneIndex < 0)
{
animatedVertex = vertex;
animatedNormal = normal;
animatedTangent = tangent;
return;
}
float4x4 attachmentToBoneTransform = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4x4, _RukhankaAttachmentToBoneTransform);
float4x4 entityRootLocalToWorld = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4x4, _RukhankaAnimatedEntityLocalToWorld);
animatedVertex = mul(attachmentToBoneTransform, float4(vertex, 1)).xyz;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_GPUATTACHMENT_RIG_SPACE_BONE_TRANSFORMS_READ, gpuBoneIndex, rigSpaceBoneTransformsBuf)
BoneTransform bt = rigSpaceBoneTransformsBuf[gpuBoneIndex];
float3 transformedPos = BoneTransform::TransformPoint(bt, animatedVertex);
animatedVertex = transformedPos;
animatedVertex = mul(entityRootLocalToWorld, float4(animatedVertex, 1)).xyz;
animatedVertex = GetCameraRelativePositionWS(animatedVertex);
animatedVertex = TransformWorldToObject(animatedVertex);
animatedNormal = mul((float3x3)attachmentToBoneTransform, normal);
animatedNormal = BoneTransform::TransformDirection(bt, animatedNormal);
animatedNormal = mul((float3x3)entityRootLocalToWorld, animatedNormal);
animatedNormal = TransformWorldToObjectDir(animatedNormal);
animatedTangent = mul((float3x3)attachmentToBoneTransform, tangent);
animatedTangent = BoneTransform::TransformDirection(bt, animatedTangent);
animatedTangent = mul((float3x3)entityRootLocalToWorld, animatedTangent);
animatedTangent = TransformWorldToObjectDir(animatedTangent);
#else
animatedVertex = vertex;
animatedNormal = normal;
animatedTangent = tangent;
#endif
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 72cf2095e0e922349b94b239dcf25a14
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/GPUAttachment/GPUAttachmentShaderNode.hlsl
uploadId: 897522
@@ -0,0 +1,67 @@
#ifndef GPU_STRUCTURES_HLSL_
#define GPU_STRUCTURES_HLSL_
/////////////////////////////////////////////////////////////////////////////////
// struct RigDefinition
ByteAddressBuffer rigDefinitions;
// struct RigBone
ByteAddressBuffer rigBones;
// struct AnimationClip
ByteAddressBuffer animationClips;
// struct HumanRotationData
ByteAddressBuffer humanRotationDataBuffer;
int animatedBonesCount;
/////////////////////////////////////////////////////////////////////////////////
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/Debug.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/BoneTransform.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AnimationClip.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/Track.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/SkinnedMeshBone.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/RigDefinition.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/RigBone.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/HumanRotationData.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AnimationToProcess.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AvatarMask.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/PerfectHashTable.hlsl"
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/SkinMatrix.hlsl"
/////////////////////////////////////////////////////////////////////////////////
struct AnimationJob
{
int rigDefinitionIndex;
int animatedBoneIndexOffset;
int2 animationsToProcessRange;
};
/////////////////////////////////////////////////////////////////////////////////
struct SkinnedMeshWorkload
{
int skinMatrixBaseOutIndex;
int boneRemapTableIndex;
int skinMatricesCount;
int rootBoneIndex;
int animatedBoneIndexOffset;
float4x4 skinnedRootBoneToEntityTransform;
};
/////////////////////////////////////////////////////////////////////////////////
struct AnimatedBoneWorkload
{
int boneIndexInRig;
int animationJobIndex;
};
/////////////////////////////////////////////////////////////////////////////////
StructuredBuffer<AnimatedBoneWorkload> animatedBoneWorkload;
StructuredBuffer<AnimationJob> animationJobs;
StructuredBuffer<AnimationToProcess> animationsToProcess;
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 73a44b85a6e0ce04896ab06edf8b01f5
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/GPUStructures.hlsl
uploadId: 897522
@@ -0,0 +1,42 @@
#ifndef MAKE_WORLD_SPACE_BONE_TRANSFORMS_HLSL_
#define MAKE_WORLD_SPACE_BONE_TRANSFORMS_HLSL_
/////////////////////////////////////////////////////////////////////////////////
RWStructuredBuffer<BoneTransform> outBoneTransforms;
StructuredBuffer<BoneTransform> boneLocalTransforms;
/////////////////////////////////////////////////////////////////////////////////
[numthreads(256, 1, 1)]
void MakeRigSpaceBoneTransforms(uint tid: SV_DispatchThreadID)
{
if (tid >= (uint)animatedBonesCount)
return;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_ANIMATED_BONE_WORKLOAD_READ, tid, animatedBoneWorkload);
AnimatedBoneWorkload boneWorkload = animatedBoneWorkload[tid];
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_ANIMATION_JOBS_READ, boneWorkload.animationJobIndex, animationJobs);
AnimationJob animationJob = animationJobs[boneWorkload.animationJobIndex];
RigDefinition rigDef = RigDefinition::ReadFromRawBuffer(rigDefinitions, animationJob.rigDefinitionIndex);
RigBone rigBone = RigBone::ReadFromRawBuffer(rigBones, rigDef.rigBonesRange.x + boneWorkload.boneIndexInRig);
int absoluteBoneIndex = animationJob.animatedBoneIndexOffset + boneWorkload.boneIndexInRig;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_BONE_LOCAL_TRANSFORMS_READ0, absoluteBoneIndex, boneLocalTransforms);
BoneTransform bt = boneLocalTransforms[absoluteBoneIndex];
int parentBoneIndex = rigBone.parentBoneIndex;
while (parentBoneIndex > 0)
{
RigBone parentBoneData = RigBone::ReadFromRawBuffer(rigBones, rigDef.rigBonesRange.x + parentBoneIndex);
int absoluteParentBoneIndex = animationJob.animatedBoneIndexOffset + parentBoneIndex;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_BONE_LOCAL_TRANSFORMS_READ1, absoluteParentBoneIndex, boneLocalTransforms);
BoneTransform parentBoneTransform = boneLocalTransforms[absoluteParentBoneIndex];
bt = BoneTransform::Multiply(parentBoneTransform, bt);
parentBoneIndex = parentBoneData.parentBoneIndex;
}
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_OUT_BONE_TRANSFORMS_WRITE, absoluteBoneIndex, outBoneTransforms);
outBoneTransforms[absoluteBoneIndex] = bt;
}
#endif // MAKE_WORLD_SPACE_BONE_TRANSFORMS_HLSL_
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: a93c55826c7c8b44c9a655d386904e4a
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/MakeRigSpaceBoneTransforms.hlsl
uploadId: 897522
@@ -0,0 +1,41 @@
#ifndef MAKE_SKIN_MATRICES_HLSL_
#define MAKE_SKIN_MATRICES_HLSL_
/////////////////////////////////////////////////////////////////////////////////
RWByteAddressBuffer outSkinMatrices;
StructuredBuffer<BoneTransform> rigSpaceBoneTransformsBuf;
StructuredBuffer<SkinnedMeshWorkload> skinMatrixWorkloadBuf;
ByteAddressBuffer skinnedMeshBoneData;
uint totalSkinnedMeshes;
/////////////////////////////////////////////////////////////////////////////////
[numthreads(128, 1, 1)]
void ComputeSkinMatrices(uint tid: SV_DispatchThreadID)
{
if (tid >= totalSkinnedMeshes)
return;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_COMPUTE_SKIN_MATRICES_SKIN_MATRIX_WORKLOAD_READ, tid, skinMatrixWorkloadBuf);
SkinnedMeshWorkload smw = skinMatrixWorkloadBuf[tid];
float4x4 w2l = smw.skinnedRootBoneToEntityTransform;
for (int i = 0; i < smw.skinMatricesCount; ++i)
{
SkinnedMeshBone smb = SkinnedMeshBone::ReadFromRawBuffer(skinnedMeshBoneData, smw.boneRemapTableIndex + i);
int boneTransformIndex = smb.boneRemapIndex + smw.animatedBoneIndexOffset;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_COMPUTE_SKIN_MATRICES_RIG_SPACE_BONE_TRANSFORMS_READ, boneTransformIndex, rigSpaceBoneTransformsBuf);
BoneTransform bt = rigSpaceBoneTransformsBuf[boneTransformIndex];
float4x4 skinMatrix = bt.ToFloat4x4();
skinMatrix = mul(w2l, skinMatrix);
float4x4 outSkinMatrix = mul(skinMatrix, smb.bindPose);
int skinMatrixOutIndex = smw.skinMatrixBaseOutIndex + i;
SkinMatrix::WriteToRawBuffer(outSkinMatrices, (float3x4)outSkinMatrix, skinMatrixOutIndex);
}
}
#endif //MAKE_SKIN_MATRICES_HLSL_
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: f0c8e199e88b9894b92573b0636c8e6b
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/MakeSkinMatrices.hlsl
uploadId: 897522
@@ -0,0 +1,246 @@
#ifndef PROCESS_ANIMATIONS_HLSL_
#define PROCESS_ANIMATIONS_HLSL_
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/TrackGroupSampler.hlsl"
/////////////////////////////////////////////////////////////////////////////////
#define NUM_MAXIMUM_LAYER_WEIGHTS 16
RWStructuredBuffer<BoneTransform> outAnimatedBones;
/////////////////////////////////////////////////////////////////////////////////
struct LayerInfo
{
int index;
float weight;
int blendMode;
};
/////////////////////////////////////////////////////////////////////////////////
float2 NormalizeAnimationTime(float at, AnimationClip ac)
{
at += ac.cycleOffset;
if (at < 0) at = 1 + at;
float normalizedTime = ac.IsLooped() ? frac(at) : saturate(at);
float timeInSeconds = normalizedTime * ac.length;
float2 rv = float2(timeInSeconds, normalizedTime);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransform MakeAdditiveAnimation(BoneTransform bonePose, BoneTransform zeroFramePose)
{
BoneTransform rv;
rv.pos = bonePose.pos - zeroFramePose.pos;
Quaternion conjugateZFRot = Quaternion::NormalizeSafe(Quaternion::Conjugate(zeroFramePose.rot));
conjugateZFRot = Quaternion::ShortestRotation(bonePose.rot, conjugateZFRot);
rv.rot = Quaternion::Multiply(conjugateZFRot, Quaternion::Normalize(bonePose.rot));
rv.scale = bonePose.scale / zeroFramePose.scale;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransform CalculateLoopPose(BoneTransform bonePose, TrackSet ts, HumanRotationData hrd, float normalizedTime)
{
float lerpFactor = normalizedTime;
TrackSampler ffSampler = CreateFirstFrameTrackSampler();
BoneTransformAndFlags rootPoseStart = SampleTrackGroup(ts, ffSampler, hrd);
TrackSampler lfSampler = CreateLastFrameTrackSampler();
BoneTransformAndFlags rootPoseEnd = SampleTrackGroup(ts, lfSampler, hrd);
float3 dPos = rootPoseEnd.bt.pos - rootPoseStart.bt.pos;
Quaternion dRot = Quaternion::Multiply(Quaternion::Conjugate(rootPoseEnd.bt.rot), rootPoseStart.bt.rot);
BoneTransform rv;
rv.pos = bonePose.pos - dPos * lerpFactor;
rv.rot = Quaternion::Multiply(bonePose.rot, Quaternion::Slerp(Quaternion::Identity(), dRot, lerpFactor));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
bool SampleAnimation(AnimationClip ac, uint baseAddress, float2 animTime, int rigBoneIndex, uint rigBoneHash, int blendMode, HumanRotationData hrd, out BoneTransformAndFlags btf)
{
btf = BoneTransformAndFlags::Identity();
TrackSet tsClip = ac.clipTracks;
tsClip.OffsetByAddress(baseAddress);
int trackGroupIndex = tsClip.GetTrackGroupIndex(rigBoneHash);
if (trackGroupIndex < 0)
return false;
tsClip.trackGroupsOffset += trackGroupIndex * 4;
float timeInSeconds = animTime.x;
TrackSampler tSampler = CreateDefaultTrackSampler(timeInSeconds);
btf = SampleTrackGroup(tsClip, tSampler, hrd);
if (blendMode == BLEND_MODE_ADDITIVE)
{
TrackSet additiveTrackSet = ac.clipTracks;
if (ac.additiveReferencePoseTracks.keyFramesOffset >= 0)
additiveTrackSet = ac.additiveReferencePoseTracks;
additiveTrackSet.OffsetByAddress(baseAddress);
int additiveTrackGroupIndex = QueryPerfectHashTable(rigBoneHash, additiveTrackSet.trackGroupPHTSeed, additiveTrackSet.trackGroupPHTOffset, additiveTrackSet.trackGroupPHTSizeMask);
if (additiveTrackGroupIndex >= 0)
{
TrackSampler ffSampler = CreateFirstFrameTrackSampler();
additiveTrackSet.trackGroupsOffset += additiveTrackGroupIndex * 4;
BoneTransformAndFlags additiveFramePose = SampleTrackGroup(additiveTrackSet, ffSampler, hrd);
btf.bt = MakeAdditiveAnimation(btf.bt, additiveFramePose.bt);
}
}
bool calculateLoopPose = ac.LoopPoseBlend() & rigBoneIndex != 0;
if (calculateLoopPose)
{
btf.bt = CalculateLoopPose(btf.bt, tsClip, hrd, animTime.y);
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransform AppendScaledPose(BoneTransform curPose, BoneTransform addedPose, float weight)
{
BoneTransform rv;
rv.pos = curPose.pos + addedPose.pos * weight;
rv.rot = Quaternion::Nlerp(curPose.rot, addedPose.rot, weight);
rv.scale = curPose.scale + addedPose.scale * weight;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransform BlendLayerPose(BoneTransform curPose, BoneTransform layerPose, BoneTransform refPose, LayerInfo layerInfo, float weightSum, float3 layerFlags)
{
BoneTransform rv = curPose;
// Override
if (layerInfo.blendMode == BLEND_MODE_OVERRIDE)
{
if (weightSum < 1)
layerPose = AppendScaledPose(layerPose, refPose, max(0, 1 - weightSum));
if (layerFlags.x > 0)
rv.pos = lerp(curPose.pos, layerPose.pos, layerInfo.weight);
if (layerFlags.y > 0)
{
layerPose.rot = Quaternion::ShortestRotation(curPose.rot, layerPose.rot);
rv.rot = Quaternion::Nlerp(curPose.rot, layerPose.rot, layerInfo.weight);
}
if (layerFlags.z > 0)
rv.scale = lerp(curPose.scale, layerPose.scale, layerInfo.weight);
}
// Additive
else
{
if (layerFlags.x > 0)
rv.pos = curPose.pos + layerPose.pos * layerInfo.weight;
if (layerFlags.y > 0)
{
Quaternion layerRot;
layerRot.value = float4(layerPose.rot.value.xyz * layerInfo.weight, layerPose.rot.value.w);
layerRot = Quaternion::NormalizeSafe(layerRot);
layerRot = Quaternion::ShortestRotation(curPose.rot, layerRot);
rv.rot = Quaternion::Multiply(curPose.rot, layerRot);
}
if (layerFlags.z > 0)
rv.scale = curPose.scale * lerp(1, layerPose.scale, layerInfo.weight);
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
LayerInfo GetLayerInfoFromAnimation(AnimationToProcess atp)
{
LayerInfo rv;
rv.weight = atp.layerWeight;
rv.blendMode = atp.blendMode;
rv.index = atp.layerIndex;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
[numthreads(128, 1, 1)]
void ProcessAnimations(uint tid: SV_DispatchThreadID)
{
if (tid >= (uint)animatedBonesCount)
return;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_ANIMATED_BONE_WORKLOAD_READ, tid, animatedBoneWorkload);
AnimatedBoneWorkload boneWorkload = animatedBoneWorkload[tid];
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_ANIMATION_JOBS_READ, boneWorkload.animationJobIndex, animationJobs);
AnimationJob animationJob = animationJobs[boneWorkload.animationJobIndex];
RigDefinition rigDef = RigDefinition::ReadFromRawBuffer(rigDefinitions, animationJob.rigDefinitionIndex);
RigBone rigBone = RigBone::ReadFromRawBuffer(rigBones, rigDef.rigBonesRange.x + boneWorkload.boneIndexInRig);
HumanRotationData hrd = (HumanRotationData)0;
if (rigDef.humanRotationDataRange.x >= 0)
hrd = HumanRotationData::ReadFromRawBuffer(humanRotationDataBuffer, rigDef.humanRotationDataRange.x + boneWorkload.boneIndexInRig);
BoneTransform blendedBonePose = rigBone.refPose;
BoneTransform layerPose = BoneTransform::Zero();
float weightSum = 0;
float3 layerFlags = 0;
LayerInfo layerInfo = (LayerInfo)0;
int atpIndexStart = animationJob.animationsToProcessRange.x;
int atpIndexEnd = animationJob.animationsToProcessRange.x + animationJob.animationsToProcessRange.y;
for (int i = atpIndexStart; i < atpIndexEnd; ++i)
{
AnimationToProcess atp = animationsToProcess[i];
bool inAvatarMask = IsBoneInAvatarMask(atp.avatarMaskDataOffset, rigBone.humanBodyPart, boneWorkload.boneIndexInRig);
if (atp.animationClipAddress < 0 || atp.weight == 0 || atp.layerWeight == 0 || !inAvatarMask)
continue;
LayerInfo curLayerInfo = GetLayerInfoFromAnimation(atp);
if (layerInfo.index != curLayerInfo.index)
{
blendedBonePose = BlendLayerPose(blendedBonePose, layerPose, rigBone.refPose, layerInfo, weightSum, layerFlags);
weightSum = 0;
layerFlags = 0;
layerPose = BoneTransform::Zero();
}
layerInfo = curLayerInfo;
int baseAddress = atp.animationClipAddress;
AnimationClip ac = AnimationClip::ReadFromRawBuffer(animationClips, baseAddress);
float2 animTime = NormalizeAnimationTime(atp.time, ac);
BoneTransformAndFlags btf;
if (SampleAnimation(ac, baseAddress, animTime, boneWorkload.boneIndexInRig, rigBone.hash, atp.blendMode, hrd, btf))
{
weightSum += atp.weight;
layerFlags += btf.flags;
layerPose = AppendScaledPose(layerPose, btf.bt, atp.weight);
}
}
blendedBonePose = BlendLayerPose(blendedBonePose, layerPose, rigBone.refPose, layerInfo, weightSum, layerFlags);
int outIndex = animationJob.animatedBoneIndexOffset + boneWorkload.boneIndexInRig;
CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_OUT_ANIMATED_BONES_WRITE, outIndex, outAnimatedBones);
outAnimatedBones[outIndex] = blendedBonePose;
}
#endif // PROCESS_ANIMATIONS_HLSL_
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 82f7bcf8fbdb6044181d80fc661b5392
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/ProcessAnimations.hlsl
uploadId: 897522
@@ -0,0 +1,146 @@
#ifndef TRACK_GROUP_SAMPLER_HLSL_
#define TRACK_GROUP_SAMPLER_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/TrackSampler.hlsl"
/////////////////////////////////////////////////////////////////////////////////
struct BoneTransformAndFlags
{
BoneTransform bt;
float3 flags;
static BoneTransformAndFlags Identity()
{
BoneTransformAndFlags rv;
rv.bt = BoneTransform::Identity();
rv.flags = 0;
return rv;
}
};
/////////////////////////////////////////////////////////////////////////////////
Quaternion ApplyHumanoidPostTransform(HumanRotationData hrd, Quaternion q)
{
Quaternion rv = Quaternion::Multiply(Quaternion::Multiply(hrd.preRot, q), hrd.postRot);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
float3 MuscleRangeToRadians(float3 minA, float3 maxA, float3 muscle)
{
float3 negativeRange = min(muscle, 0);
float3 positiveRange = max(0, muscle);
float3 negativeRot = lerp(0, minA, -negativeRange);
float3 positiveRot = lerp(0, maxA, +positiveRange);
float3 rv = negativeRot + positiveRot;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Quaternion MuscleValuesToQuaternion(HumanRotationData humanBoneInfo, float3 muscleValues)
{
float3 r = MuscleRangeToRadians(humanBoneInfo.minMuscleAngles, humanBoneInfo.maxMuscleAngles, muscleValues);
r *= humanBoneInfo.sign;
float3 rightVec = float3(1, 0, 0);
float3 upVec = float3(0, 1, 0);
float3 forwardVec = float3(0, 0, 1);
Quaternion qx = Quaternion::AxisAngle(rightVec, r.x);
Quaternion qy = Quaternion::AxisAngle(upVec, r.y);
Quaternion qz = Quaternion::AxisAngle(forwardVec, r.z);
Quaternion qzy = Quaternion::Multiply(qz, qy);
qzy.value.x = 0;
Quaternion rv = Quaternion::Multiply(Quaternion::Normalize(qzy), qx);
rv = ApplyHumanoidPostTransform(humanBoneInfo, rv);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransformAndFlags SampleTrackGroup(TrackSet ts, TrackSampler trackSampler, HumanRotationData hrd)
{
int trackStartIndex = animationClips.Load(ts.trackGroupsOffset);
int trackEndIndex = animationClips.Load(ts.trackGroupsOffset + 4);
float pos[3] = {0, 0, 0};
float rot[4] = {0, 0, 0, 1};
float scale[3] = {1, 1, 1};
float3 flags = 0;
bool eulerToQuaternion = false;
bool isHumanMuscle = false;
for (int i = trackStartIndex; i < trackEndIndex; ++i)
{
Track tk = Track::ReadFromRawBuffer(animationClips, ts.tracksOffset, i);
int channelIndex = tk.GetChannelIndex();
float interpolatedCurveValue = trackSampler.Sample(tk, ts.keyFramesOffset);
switch (tk.GetBindingType())
{
case BINDING_TYPE_TRANSLATION:
pos[channelIndex] = interpolatedCurveValue;
flags.x = 1;
break;
case BINDING_TYPE_QUATERNION:
rot[channelIndex] = interpolatedCurveValue;
flags.y = 1;
break;
case BINDING_TYPE_SCALE:
scale[channelIndex] = interpolatedCurveValue;
flags.z = 1;
break;
case BINDING_TYPE_EULER_ANGLES:
eulerToQuaternion = true;
rot[channelIndex] = interpolatedCurveValue;
flags.y = 1;
break;
case BINDING_TYPE_HUMAN_MUSCLE:
rot[channelIndex] = interpolatedCurveValue;
isHumanMuscle = true;
flags.y = 1;
break;
}
}
if (eulerToQuaternion)
{
float3 eulerAnglesInDegrees = float3(rot[0], rot[1], rot[2]);
Quaternion q = Quaternion::EulerXYZ(eulerAnglesInDegrees * (1 / 180.0f * 3.1415926f));
rot[0] = q.value.x;
rot[1] = q.value.y;
rot[2] = q.value.z;
rot[3] = q.value.w;
}
if (isHumanMuscle)
{
float3 muscleValues = float3(rot[0], rot[1], rot[2]);
Quaternion q = MuscleValuesToQuaternion(hrd, muscleValues);
rot[0] = q.value.x;
rot[1] = q.value.y;
rot[2] = q.value.z;
rot[3] = q.value.w;
}
BoneTransform bt = BoneTransform::Identity();
bt.pos = float3(pos[0], pos[1], pos[2]);
bt.rot.value = float4(rot[0], rot[1], rot[2], rot[3]);
bt.scale = float3(scale[0], scale[1], scale[2]);
BoneTransformAndFlags rv;
rv.bt = bt;
rv.flags = flags;
return rv;
};
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 17fe18272a1400f40bd8ff451251ad4f
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/TrackGroupSampler.hlsl
uploadId: 897522
@@ -0,0 +1,62 @@
#ifndef TRACK_SAMPLER_HLSL_
#define TRACK_SAMPLER_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#define TRACK_SAMPLER_TYPE_DEFAULT 0
#define TRACK_SAMPLER_TYPE_FIRST_FRAME 1
#define TRACK_SAMPLER_TYPE_LAST_FRAME 2
/////////////////////////////////////////////////////////////////////////////////
struct TrackSampler
{
float time;
int samplerType;
float Sample(Track tk, int keyFrameBaseAddress)
{
// With absence of interfaces in DXC and templates (hello HLSL 2021) need to invent such apprach
switch (samplerType)
{
case TRACK_SAMPLER_TYPE_DEFAULT:
return tk.SampleByBinarySearch(time, keyFrameBaseAddress);
break;
case TRACK_SAMPLER_TYPE_FIRST_FRAME:
return tk.GetFirstFrameValue(keyFrameBaseAddress);
break;
case TRACK_SAMPLER_TYPE_LAST_FRAME:
return tk.GetLastFrameValue(keyFrameBaseAddress);
break;
}
return 0;
}
};
//-----------------------------------------------------------------------------------------//
// Helper functions to create typed samplers
//-----------------------------------------------------------------------------------------//
TrackSampler CreateDefaultTrackSampler(float time)
{
TrackSampler rv = {time, TRACK_SAMPLER_TYPE_DEFAULT};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
TrackSampler CreateFirstFrameTrackSampler()
{
TrackSampler rv = {0, TRACK_SAMPLER_TYPE_FIRST_FRAME};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
TrackSampler CreateLastFrameTrackSampler()
{
TrackSampler rv = {0, TRACK_SAMPLER_TYPE_LAST_FRAME};
return rv;
}
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 4000a1fb1ac3eec4fb7dfdac3670dafb
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/GPUAnimationEngine/Resources/TrackSampler.hlsl
uploadId: 897522