Skip to content

Commit

Permalink
chore: batch update Avatar skinning matrices (#1855)
Browse files Browse the repository at this point in the history
* First working version

* Using unsafe pointers

* Proof of concept is valid!

* Removed unnecesasry cleanup, pass the NativeArray entirely and read by index, some clenaup

* Reuse and resize strategy

* Added NetworkAvatar property

* Delete avatar transform matrix

* Added test suite

* Naming convetions for testing

* Moved FinshAvatarMatricesCaluclation to PreRenderingSystemGroup

* Applied suggestions

* Some cleanup

* Further merge

---------

Co-authored-by: Mikhail Agapov <[email protected]>
  • Loading branch information
dalkia and mikhail-dcl authored Sep 23, 2024
1 parent b5be150 commit 2d0dcdb
Show file tree
Hide file tree
Showing 24 changed files with 453 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"GUID:8322ea9340a544c59ddc56d4793eac74",
"GUID:166b65e6dfc848bb9fb075f53c293a38",
"GUID:e25ef972de004615a22937e739de2def",
"GUID:543b8f091a5947a3880b7f2bca2358bd"
"GUID:543b8f091a5947a3880b7f2bca2358bd",
"GUID:d832748739a186646b8656bdbd447ad0"
],
"includePlatforms": [],
"excludePlatforms": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,18 @@

namespace DCL.AvatarRendering.AvatarShape.Components
{
public struct AvatarTransformMatrixComponent : IDisposable
public struct AvatarTransformMatrixComponent
{
private TransformAccessArray bones;
private BoneMatrixCalculationJob job;
private JobHandle handle;
public int IndexInGlobalJobArray;
public Transform[] bones;

internal bool disposed { get; private set; }

internal bool completed { get; private set; }

public void ScheduleBoneMatrixCalculation(Matrix4x4 avatarWorldToLocalMatrix)
{
if (disposed)
throw new ObjectDisposedException(nameof(AvatarTransformMatrixComponent), $"{nameof(ScheduleBoneMatrixCalculation)} called on the disposed component");

if (!handle.IsCompleted)
return;

job.AvatarTransform = avatarWorldToLocalMatrix;
handle = job.Schedule(bones);
completed = false;
}

public NativeArray<float4x4> CompleteBoneMatrixCalculations()
public static AvatarTransformMatrixComponent Create(Transform[] bones)
{
handle.Complete();
completed = true;
return job.BonesMatricesResult;
}

public void Dispose()
{
handle.Complete();
job.BonesMatricesResult.Dispose();
bones.Dispose();

disposed = true;
completed = true;
}

public static AvatarTransformMatrixComponent Create(Transform avatarBaseTransform, Transform[] bones) =>
new ()
return new AvatarTransformMatrixComponent
{
bones = new TransformAccessArray(bones),
job = new BoneMatrixCalculationJob
{
BonesMatricesResult = new NativeArray<float4x4>(bones.Length, Allocator.Persistent),
AvatarTransform = avatarBaseTransform.worldToLocalMatrix,
},
IndexInGlobalJobArray = -1,
bones = bones
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DCL.AvatarRendering.AvatarShape.ComputeShader;
using DCL.AvatarRendering.AvatarShape.UnityInterface;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;

namespace DCL.AvatarRendering.AvatarShape.Components
{
public unsafe class AvatarTransformMatrixJobWrapper : IDisposable
{
internal const int AVATAR_ARRAY_SIZE = 100;
private static readonly int BONES_ARRAY_LENGTH = ComputeShaderConstants.BONE_COUNT;
private static readonly int BONES_PER_AVATAR_LENGTH = AVATAR_ARRAY_SIZE * BONES_ARRAY_LENGTH;

internal NativeArray<Matrix4x4> matrixFromAllAvatars;
private Matrix4x4* matrixPtr;

internal NativeArray<bool> updateAvatar;
private bool* updateAvatarPtr;

private TransformAccessArray bonesCombined;
public BoneMatrixCalculationJob job;

private JobHandle handle;

private readonly Stack<int> releasedIndexes;

private int avatarIndex;
private int nextResizeValue;
internal int currentAvatarAmountSupported;

public AvatarTransformMatrixJobWrapper()
{
job = new BoneMatrixCalculationJob(BONES_ARRAY_LENGTH, BONES_PER_AVATAR_LENGTH);

bonesCombined = new TransformAccessArray(BONES_PER_AVATAR_LENGTH);
for (int i = 0; i < BONES_PER_AVATAR_LENGTH; i++)
bonesCombined.Add(null);

matrixFromAllAvatars
= new NativeArray<Matrix4x4>(AVATAR_ARRAY_SIZE, Allocator.Persistent);
matrixPtr = (Matrix4x4*)matrixFromAllAvatars.GetUnsafePtr();

updateAvatar = new NativeArray<bool>(AVATAR_ARRAY_SIZE, Allocator.Persistent);
updateAvatarPtr = (bool*)updateAvatar.GetUnsafePtr();

currentAvatarAmountSupported = AVATAR_ARRAY_SIZE;

nextResizeValue = 2;
releasedIndexes = new Stack<int>();
}


public void ScheduleBoneMatrixCalculation()
{
job.AvatarTransform = matrixFromAllAvatars;
job.UpdateAvatar = updateAvatar;
handle = job.Schedule(bonesCombined);
}

public void CompleteBoneMatrixCalculations()
{
handle.Complete();
}

public void UpdateAvatar(AvatarBase avatarBase, ref AvatarTransformMatrixComponent transformMatrixComponent)
{
if (transformMatrixComponent.IndexInGlobalJobArray == -1)
{
if (releasedIndexes.Count > 0)
transformMatrixComponent.IndexInGlobalJobArray = releasedIndexes.Pop();
else
{
transformMatrixComponent.IndexInGlobalJobArray = avatarIndex;
avatarIndex++;
}

//Add all bones to the bonesCombined array with the current available index
for (int i = 0; i < BONES_ARRAY_LENGTH; i++)
bonesCombined[transformMatrixComponent.IndexInGlobalJobArray * BONES_ARRAY_LENGTH + i] =
transformMatrixComponent.bones[i];
}

//Setup of data
matrixPtr[transformMatrixComponent.IndexInGlobalJobArray] = avatarBase.transform.worldToLocalMatrix;
updateAvatarPtr[transformMatrixComponent.IndexInGlobalJobArray] = true;

if (avatarIndex >= currentAvatarAmountSupported - 1)
ResizeArrays();
}

private void ResizeArrays()
{
var newBonesCombined
= new TransformAccessArray(BONES_PER_AVATAR_LENGTH * nextResizeValue);
for (var i = 0; i < BONES_PER_AVATAR_LENGTH * nextResizeValue; i++)
{
if (i < BONES_PER_AVATAR_LENGTH * (nextResizeValue - 1))
newBonesCombined.Add(bonesCombined[i]);
else
newBonesCombined.Add(null);
}

bonesCombined.Dispose();
bonesCombined = newBonesCombined;

var newMatrixFromAllAvatars
= new NativeArray<Matrix4x4>(AVATAR_ARRAY_SIZE * nextResizeValue, Allocator.Persistent);
UnsafeUtility.MemCpy(newMatrixFromAllAvatars.GetUnsafePtr(), matrixFromAllAvatars.GetUnsafePtr(),
matrixFromAllAvatars.Length * sizeof(Matrix4x4));
matrixFromAllAvatars.Dispose();
matrixFromAllAvatars = newMatrixFromAllAvatars;
matrixPtr = (Matrix4x4*)matrixFromAllAvatars.GetUnsafePtr();

var newUpdateAvatar
= new NativeArray<bool>(AVATAR_ARRAY_SIZE * nextResizeValue, Allocator.Persistent);
UnsafeUtility.MemCpy(newUpdateAvatar.GetUnsafePtr(), updateAvatar.GetUnsafePtr(),
updateAvatar.Length * sizeof(bool));
updateAvatar.Dispose();
updateAvatar = newUpdateAvatar;
updateAvatarPtr = (bool*)updateAvatar.GetUnsafePtr();

job.BonesMatricesResult.Dispose();
job = new BoneMatrixCalculationJob(BONES_ARRAY_LENGTH, BONES_PER_AVATAR_LENGTH * nextResizeValue);

currentAvatarAmountSupported = AVATAR_ARRAY_SIZE * nextResizeValue;
nextResizeValue++;
}

public void Dispose()
{
handle.Complete();
bonesCombined.Dispose();
updateAvatar.Dispose();
job.BonesMatricesResult.Dispose();
}

public void ReleaseAvatar(ref AvatarTransformMatrixComponent avatarTransformMatrixComponent)
{
if (avatarTransformMatrixComponent.IndexInGlobalJobArray == -1)
return;

//Dont update this index anymore until reset
updateAvatarPtr[avatarTransformMatrixComponent.IndexInGlobalJobArray] = false;
releasedIndexes.Push(avatarTransformMatrixComponent.IndexInGlobalJobArray);

avatarTransformMatrixComponent.IndexInGlobalJobArray = -1;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,32 @@ namespace DCL.AvatarRendering.AvatarShape.ComputeShader
[BurstCompile]
public struct BoneMatrixCalculationJob : IJobParallelForTransform
{
private readonly int BONE_COUNT;
private int AvatarIndex;

public NativeArray<float4x4> BonesMatricesResult;
public Matrix4x4 AvatarTransform;
[NativeDisableParallelForRestriction]
public NativeArray<Matrix4x4> AvatarTransform;

[NativeDisableParallelForRestriction] public NativeArray<bool> UpdateAvatar;

public BoneMatrixCalculationJob(int boneCount, int bonesPerAvatarLength)
{
BONE_COUNT = boneCount;
BonesMatricesResult = new NativeArray<float4x4>(bonesPerAvatarLength, Allocator.Persistent);
AvatarTransform = default;
UpdateAvatar = default;
AvatarIndex = 0;
}

public void Execute(int index, TransformAccess transform)
{
BonesMatricesResult[index] = AvatarTransform * transform.localToWorldMatrix;
// The avatarIndex is calculated by dividing the index by the amount of bones per avatar
// Therefore, all of the indexes between 0 and ComputeShaderConstants.BONE_COUNT correlates to a single avatar
AvatarIndex = index / BONE_COUNT;
if (!UpdateAvatar[AvatarIndex])
return;
BonesMatricesResult[index] = AvatarTransform[AvatarIndex] * transform.localToWorldMatrix;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ public override AvatarCustomSkinningComponent Initialize(IList<CachedAttachment>

return new AvatarCustomSkinningComponent(vertCount, buffers, materialSetups, skinningShader, totalBounds);
}
public override void ComputeSkinning(NativeArray<float4x4> bonesResult, ref AvatarCustomSkinningComponent skinning)

public override void ComputeSkinning(NativeArray<float4x4> bonesResult, int indexInGlobalResultArray, ref AvatarCustomSkinningComponent skinning)
{
skinning.buffers.bones.SetData(bonesResult);
skinning.buffers.bones.SetData(bonesResult, indexInGlobalResultArray * ComputeShaderConstants.BONE_COUNT, 0 , ComputeShaderConstants.BONE_COUNT);
skinning.computeShaderInstance.Dispatch(skinning.buffers.kernel, (skinning.vertCount / 64) + 1, 1, 1);

//Note (Juani): According to Unity, BeginWrite/EndWrite works better than SetData. But we got inconsitent result using ComputeBufferMode.SubUpdates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public abstract AvatarCustomSkinningComponent Initialize(IList<CachedAttachment>
UnityEngine.ComputeShader skinningShader, IAvatarMaterialPoolHandler avatarMaterial,
AvatarShapeComponent avatarShapeComponent, in FacialFeaturesTextures facialFeatureTexture);

public abstract void ComputeSkinning(NativeArray<float4x4> bonesResult, ref AvatarCustomSkinningComponent skinning);
public abstract void ComputeSkinning(NativeArray<float4x4> bonesResult, int indexInGlobalResultArray, ref AvatarCustomSkinningComponent skinning);

private protected abstract AvatarCustomSkinningComponent.MaterialSetup SetupMaterial(Renderer meshRenderer, Material originalMaterial, int lastWearableVertCount, IAvatarMaterialPoolHandler celShadingMaterial,
AvatarShapeComponent shapeComponent, in FacialFeaturesTextures facialFeaturesTextures);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ namespace DCL.AvatarRendering.AvatarShape.Helpers
public static class ReleaseAvatar
{
public static void Execute(FixedComputeBufferHandler vertOutBuffer, IAttachmentsAssetsCache wearableAssetsCache,
IAvatarMaterialPoolHandler avatarMaterialPoolHandler, IObjectPool<UnityEngine.ComputeShader> computeShaderSkinningPool,
in AvatarShapeComponent avatarShapeComponent, ref AvatarCustomSkinningComponent skinningComponent)
IAvatarMaterialPoolHandler avatarMaterialPoolHandler,
IObjectPool<UnityEngine.ComputeShader> computeShaderSkinningPool,
in AvatarShapeComponent avatarShapeComponent, ref AvatarCustomSkinningComponent skinningComponent,
ref AvatarTransformMatrixComponent avatarTransformMatrixComponent,
AvatarTransformMatrixJobWrapper jobWrapper)
{
vertOutBuffer.Release(skinningComponent.VertsOutRegion);
skinningComponent.Dispose(avatarMaterialPoolHandler, computeShaderSkinningPool);

jobWrapper.ReleaseAvatar(ref avatarTransformMatrixComponent);

if (avatarShapeComponent.WearablePromise.IsConsumed)
wearableAssetsCache.ReleaseAssets(avatarShapeComponent.InstantiatedWearables);
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public partial class AvatarCleanUpSystem : BaseUnityLoopSystem
private readonly IObjectPool<UnityEngine.ComputeShader> computeShaderSkinningPool;
private readonly IAttachmentsAssetsCache wearableAssetsCache;

private readonly AvatarTransformMatrixJobWrapper avatarTransformMatrixBatchJob;


internal AvatarCleanUpSystem(
World world,
IPerformanceBudget instantiationFrameTimeBudget,
Expand All @@ -41,7 +44,8 @@ internal AvatarCleanUpSystem(
IComponentPool<AvatarBase> avatarPoolRegistry,
IObjectPool<UnityEngine.ComputeShader> computeShaderSkinningPool,
IAttachmentsAssetsCache wearableAssetsCache,
ObjectProxy<AvatarBase> mainPlayerAvatarBaseProxy) : base(world)
ObjectProxy<AvatarBase> mainPlayerAvatarBaseProxy,
AvatarTransformMatrixJobWrapper avatarTransformMatrixBatchJob) : base(world)
{
this.instantiationFrameTimeBudget = instantiationFrameTimeBudget;
this.vertOutBuffer = vertOutBuffer;
Expand All @@ -50,6 +54,7 @@ internal AvatarCleanUpSystem(
this.computeShaderSkinningPool = computeShaderSkinningPool;
this.wearableAssetsCache = wearableAssetsCache;
this.mainPlayerAvatarBaseProxy = mainPlayerAvatarBaseProxy;
this.avatarTransformMatrixBatchJob = avatarTransformMatrixBatchJob;
}

protected override void Update(float t)
Expand Down Expand Up @@ -85,7 +90,7 @@ private void DestroyAvatar(ref AvatarShapeComponent avatarShapeComponent, ref Av
deleteEntityIntention.DeferDeletion = true;
return;
}

InternalDestroyAvatar(ref avatarShapeComponent, ref skinningComponent, ref avatarTransformMatrixComponent, avatarBase);
deleteEntityIntention.DeferDeletion = false;
}
Expand All @@ -97,9 +102,10 @@ private void InternalDestroyAvatar(ref AvatarShapeComponent avatarShapeComponent
if (mainPlayerAvatarBaseProxy.Object == avatarBase)
mainPlayerAvatarBaseProxy.ReleaseObject();

ReleaseAvatar.Execute(vertOutBuffer, wearableAssetsCache, avatarMaterialPoolHandler, computeShaderSkinningPool, avatarShapeComponent, ref skinningComponent);
ReleaseAvatar.Execute(vertOutBuffer, wearableAssetsCache, avatarMaterialPoolHandler,
computeShaderSkinningPool, avatarShapeComponent, ref skinningComponent,
ref avatarTransformMatrixComponent, avatarTransformMatrixBatchJob);

avatarTransformMatrixComponent.Dispose();
avatarPoolRegistry.Release(avatarBase);
}
}
Expand Down
Loading

0 comments on commit 2d0dcdb

Please sign in to comment.