Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1e00b5052d07c154b8701949fb1889a4
m_Address: Assets/DCL/SpringBones/SpringBonesSimulation.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 23318cb20f4dbe9429dd8be585ef75d6
m_Address: CharacterPreviewContainer
m_ReadOnly: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public MaterialSetup(TextureArraySlot?[] usedTextureArraySlots, Material usedMat
public FixedComputeBufferHandler.Slice VertsOutRegion;

public readonly int VertCount;
public readonly int BoneCount;

internal readonly List<MaterialSetup> materials;
private readonly Buffers buffers;
Expand All @@ -88,9 +89,10 @@ public MaterialSetup(TextureArraySlot?[] usedTextureArraySlots, Material usedMat
/// </summary>
public Bounds LocalBounds { get; private set; }

internal AvatarCustomSkinningComponent(int vertCount, Buffers buffers, List<MaterialSetup> materials, UnityEngine.ComputeShader computeShaderInstance, Bounds localBounds)
internal AvatarCustomSkinningComponent(int vertCount, int boneCount, Buffers buffers, List<MaterialSetup> materials, UnityEngine.ComputeShader computeShaderInstance, Bounds localBounds)
{
VertCount = vertCount;
BoneCount = boneCount;
this.buffers = buffers;
this.materials = materials;
this.computeShaderInstance = computeShaderInstance;
Expand Down Expand Up @@ -125,16 +127,9 @@ public Result ComputeSkinning(NativeArray<float4x4> bonesResult, GlobalJobArrayI
return Result.ErrorResult("ComputeSkinning error: Cannot get bones (ComputeBuffer)");
}

bones.SetData(bonesResult, validIndex * ComputeShaderConstants.BONE_COUNT, 0 , ComputeShaderConstants.BONE_COUNT);
bones.SetData(bonesResult, validIndex * ComputeShaderConstants.MAX_BONE_COUNT, 0, BoneCount);
computeShaderInstance.Dispatch(buffers.kernel, (VertCount / 64) + 1, 1, 1);
return Result.SuccessResult();

//Note (Juani): According to Unity, BeginWrite/EndWrite works better than SetData. But we got inconsitent result using ComputeBufferMode.SubUpdates
//Ash machine (AMD) worked way worse than mine (NVidia). So, we are back to SetData with a ComputeBufferMode.Dynamic, which works well for both.
//https://docs.unity3d.com/2020.1/Documentation/ScriptReference/ComputeBuffer.BeginWrite.html
/*NativeArray<float4x4> bonesIn = mBones.BeginWrite<float4x4>(0, ComputeShaderConstants.BONE_COUNT);
NativeArray<float4x4>.Copy(bonesResult, 0, bonesIn, 0, ComputeShaderConstants.BONE_COUNT);
mBones.EndWrite<float4x4>(ComputeShaderConstants.BONE_COUNT);*/
}

public void SetVertOutRegion(FixedComputeBufferHandler.Slice region)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct AvatarShapeComponent
public bool IsVisible;
public bool HiddenByModifierArea;
public bool IsPreview;
public int InstantiationVersion;

public Color SkinColor;
public Color HairColor;
Expand Down Expand Up @@ -45,6 +46,7 @@ public AvatarShapeComponent(string name, string id, BodyShape bodyShape, Wearabl
HiddenByModifierArea = false;
IsPreview = false;
ShowOnlyWearables = showOnlyWearables;
InstantiationVersion = -1;
}

public void CreateOutlineCompatibilityList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DCL.Utility.Types;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace DCL.AvatarRendering.AvatarShape.Components
Expand Down Expand Up @@ -74,25 +75,29 @@ public bool TryGetValue(out int value)
}

/// <summary>
/// Guarantees amount of bones in the array
/// Bone transform array with separate count and capacity. The internal array never shrinks —
/// <see cref="Append"/> reuses existing capacity when possible, avoiding heap allocation.
/// </summary>
public readonly struct BoneArray
public struct BoneArray
{
public const int COUNT = ComputeShaderConstants.BONE_COUNT;

public readonly Transform[] Inner;
public Transform[] Inner;
public int Count;

public Transform this[int i] => Inner[i];

private BoneArray(Transform[] inner)
private BoneArray(Transform[] inner, int count)
{
this.Inner = inner;
Inner = inner;
Count = count;
}

public static Result<BoneArray> From(Transform[] bones) =>
bones.Length != COUNT
? Result<BoneArray>.ErrorResult($"Cannot map bone array, mismatch count: real {bones.Length}, expected: {COUNT}")
: Result<BoneArray>.SuccessResult(new BoneArray(bones));
public static Result<BoneArray> From(Transform[] bones)
{
if (bones.Length < ComputeShaderConstants.BASE_BONE_COUNT || bones.Length > ComputeShaderConstants.MAX_BONE_COUNT)
return Result<BoneArray>.ErrorResult($"Cannot map bone array, count {bones.Length} outside valid range [{ComputeShaderConstants.BASE_BONE_COUNT}, {ComputeShaderConstants.MAX_BONE_COUNT}]");

return Result<BoneArray>.SuccessResult(new BoneArray(bones, bones.Length));
}

public static BoneArray FromOrDefault(Transform[] bones, ReportData reportData)
{
Expand All @@ -109,9 +114,32 @@ public static BoneArray FromOrDefault(Transform[] bones, ReportData reportData)

public static BoneArray NewDefault()
{
var inner = new Transform[COUNT];
for (int i = 0; i < COUNT; i++) inner[i] = new GameObject("BoneDefault").transform;
return new BoneArray(inner);
var inner = new Transform[ComputeShaderConstants.BASE_BONE_COUNT];
for (int i = 0; i < ComputeShaderConstants.BASE_BONE_COUNT; i++) inner[i] = new GameObject("BoneDefault").transform;
return new BoneArray(inner, ComputeShaderConstants.BASE_BONE_COUNT);
}

public void Append(List<Transform> extra, ReportData reportData)
{
if (extra.Count == 0) return;

int newCount = Count + extra.Count;

if (newCount > ComputeShaderConstants.MAX_BONE_COUNT)
{
ReportHub.LogWarning(reportData, $"Spring bone count would exceed MAX_BONE_COUNT ({ComputeShaderConstants.MAX_BONE_COUNT}), capping from {newCount} to {ComputeShaderConstants.MAX_BONE_COUNT}");
newCount = ComputeShaderConstants.MAX_BONE_COUNT;
}

if (Inner.Length < newCount)
Array.Resize(ref Inner, newCount);

int extraCount = newCount - Count;

for (int i = 0; i < extraCount; i++)
Inner[Count + i] = extra[i];

Count = newCount;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class AvatarTransformMatrixJobWrapper : IDisposable
private const int BONE_MATRIX_BATCH_COUNT = 4;

internal const int AVATAR_ARRAY_SIZE = 100;
private const int BONES_ARRAY_LENGTH = ComputeShaderConstants.BONE_COUNT;
private const int BONES_ARRAY_LENGTH = ComputeShaderConstants.MAX_BONE_COUNT;
private const int BONES_PER_AVATAR_LENGTH = AVATAR_ARRAY_SIZE * BONES_ARRAY_LENGTH;

private bool disposed;
Expand Down Expand Up @@ -74,7 +74,7 @@ public void RegisterMainPlayerAvatar(AvatarBase avatarBase, ref AvatarTransformM
transformMatrixComponent.IndexInGlobalJobArray = GlobalJobArrayIndex.ValidUnsafe(0);
transformMatrixComponent.IsMainPlayer = true;

mainPlayerAvatar.Register(avatarBase.transform, transformMatrixComponent.bones.Inner);
mainPlayerAvatar.Register(avatarBase.transform, transformMatrixComponent.bones, dummyTransform);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace DCL.AvatarRendering.AvatarShape.Components
internal class MainPlayerPipeline : IDisposable
{
private readonly int bonesArrayLength;
private readonly Transform[] boneArray;

private bool registered;
private TransformAccessArray bonesTA;
Expand All @@ -28,21 +29,28 @@ internal class MainPlayerPipeline : IDisposable
internal MainPlayerPipeline(int bonesArrayLength)
{
this.bonesArrayLength = bonesArrayLength;
boneArray = new Transform[bonesArrayLength];

bonesCombined = new NativeArray<float4x4>(bonesArrayLength, Allocator.Persistent);
avatarMatrix = new NativeArray<float4x4>(1, Allocator.Persistent);
updateFlag = new NativeArray<bool>(1, Allocator.Persistent);
Job = new BoneMatrixCalculationJob(bonesArrayLength, bonesArrayLength, bonesCombined);
}

public void Register(Transform rootTransform, Transform[] boneTransforms)
public void Register(Transform rootTransform, BoneArray bones, Transform dummyTransform)
{
updateFlag[0] = true;

var boneArray = new Transform[bonesArrayLength];
int actualCount = Mathf.Min(bones.Count, bonesArrayLength);

for (int i = 0; i < bonesArrayLength; i++)
boneArray[i] = boneTransforms[i];
for (int i = 0; i < actualCount; i++)
boneArray[i] = bones[i];

for (int i = actualCount; i < bonesArrayLength; i++)
boneArray[i] = dummyTransform;

if (bonesTA.isCreated) bonesTA.Dispose();
if (rootTA.isCreated) rootTA.Dispose();

bonesTA = new TransformAccessArray(boneArray);
rootTA = new TransformAccessArray(new[] { rootTransform });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,27 @@ public void Register(AvatarBase avatarBase, ref AvatarTransformMatrixComponent t

// Write into flat backing arrays
int offset = validIndex * bonesArrayLength;
Transform[] bones = transformMatrixComponent.bones.Inner;
BoneArray bones = transformMatrixComponent.bones;
int actualCount = Mathf.Min(bones.Count, bonesArrayLength);

for (int b = 0; b < bonesArrayLength; b++)
for (int b = 0; b < actualCount; b++)
flatBones[offset + b] = bones[b];

for (int b = actualCount; b < bonesArrayLength; b++)
flatBones[offset + b] = dummyTransform;

flatRoots[validIndex] = avatarBase.transform;
updateAvatar[validIndex] = true;

// Update TAA in-place if index is within current TAA bounds
if (bonesTransformAccessArray.isCreated && validIndex < taaSlotCount)
{
for (int b = 0; b < bonesArrayLength; b++)
for (int b = 0; b < actualCount; b++)
bonesTransformAccessArray[offset + b] = bones[b];

for (int b = actualCount; b < bonesArrayLength; b++)
bonesTransformAccessArray[offset + b] = dummyTransform;

rootsTransformAccessArray[validIndex] = avatarBase.transform;
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace DCL.AvatarRendering.AvatarShape.ComputeShader
{
public abstract class ComputeSkinningBufferContainer : IDisposable
{
internal static readonly ListObjectPool<Matrix4x4> MATRIX4X4_POOL = new (listInstanceDefaultCapacity: ComputeShaderConstants.BONE_COUNT);
internal static readonly ListObjectPool<Matrix4x4> MATRIX4X4_POOL = new (listInstanceDefaultCapacity: ComputeShaderConstants.BASE_BONE_COUNT);

//5000 is an approximation of a top value a wearable may have for its vertex count
internal static readonly ListObjectPool<Vector3> VECTOR3_POOL = new (defaultCapacity: 2, listInstanceDefaultCapacity: 5000);
Expand Down Expand Up @@ -74,18 +74,67 @@ public void SetBuffers(UnityEngine.ComputeShader cs, int kernel)
cs.SetBuffer(kernel, ComputeShaderConstants.BIND_POSES_INDEX_ID, bindPosesIndex);
}

public void CopyAllBuffers(Mesh mesh, int currentMeshVertexCount, int vertexCounter, int skinnedMeshCounter)
public void CopyAllBuffers(Mesh mesh, int currentMeshVertexCount, int vertexCounter, int skinnedMeshCounter, int boneCount, int springBoneOffset = 0)
{
List<Matrix4x4> bindPosesList = MATRIX4X4_POOL.Get();
mesh.GetBindposes(bindPosesList);
NativeArray<Matrix4x4>.Copy(UnsafeUtility.As<List<Matrix4x4>, ListPrivateFieldAccess<Matrix4x4>>(ref bindPosesList)._items, 0, bindPosesMatrix, ComputeShaderConstants.BONE_COUNT * skinnedMeshCounter, ComputeShaderConstants.BONE_COUNT);

int bindPoseCount = bindPosesList.Count;
int destOffset = boneCount * skinnedMeshCounter;
Matrix4x4[] bindPosesItems = UnsafeUtility.As<List<Matrix4x4>, ListPrivateFieldAccess<Matrix4x4>>(ref bindPosesList)._items;

// Initialize all slots to identity, then overwrite with actual bind poses.
// Spring bone bind poses are placed at the offset position to match the offset BoneWeight indices,
// since the compute shader uses the same index for both g_mBones[idx] and g_BindPoses[baseIndex+idx].
for (int i = 0; i < boneCount; i++)
bindPosesMatrix[destOffset + i] = Matrix4x4.identity;

// Copy base skeleton bind poses (indices 0..BASE_BONE_COUNT-1) as-is
int baseCopyCount = Math.Min(bindPoseCount, ComputeShaderConstants.BASE_BONE_COUNT);
NativeArray<Matrix4x4>.Copy(bindPosesItems, 0, bindPosesMatrix, destOffset, baseCopyCount);

// Copy spring bone bind poses to the offset position so they align with the offset BoneWeight indices
int springBindPoseStart = ComputeShaderConstants.BASE_BONE_COUNT;

if (bindPoseCount > springBindPoseStart)
{
int springBindPoseCount = bindPoseCount - springBindPoseStart;
int springDestIndex = destOffset + springBindPoseStart + springBoneOffset;

int maxCopy = boneCount - (springBindPoseStart + springBoneOffset);

if (maxCopy > 0)
{
int copyCount = Math.Min(springBindPoseCount, maxCopy);
NativeArray<Matrix4x4>.Copy(bindPosesItems, springBindPoseStart, bindPosesMatrix, springDestIndex, copyCount);
}
}

MATRIX4X4_POOL.Release(bindPosesList);

List<BoneWeight> boneWeightPool = BONE_WEIGHT_POOL.Get();
mesh.GetBoneWeights(boneWeightPool);
NativeArray<BoneWeight>.Copy(UnsafeUtility.As<List<BoneWeight>, ListPrivateFieldAccess<BoneWeight>>(ref boneWeightPool)._items, 0, totalSkinIn, vertexCounter, currentMeshVertexCount);
BONE_WEIGHT_POOL.Release(boneWeightPool);

// Offset spring bone indices so each wearable's spring bones map to the correct
// slot in the global bone matrix buffer (g_mBones). Without this, multiple wearables
// with spring bones would all reference the same bone indices (starting at BASE_BONE_COUNT).
if (springBoneOffset > 0)
{
for (int i = 0; i < currentMeshVertexCount; i++)
{
BoneWeight bw = totalSkinIn[vertexCounter + i];

if (bw.boneIndex0 >= ComputeShaderConstants.BASE_BONE_COUNT) bw.boneIndex0 += springBoneOffset;
if (bw.boneIndex1 >= ComputeShaderConstants.BASE_BONE_COUNT) bw.boneIndex1 += springBoneOffset;
if (bw.boneIndex2 >= ComputeShaderConstants.BASE_BONE_COUNT) bw.boneIndex2 += springBoneOffset;
if (bw.boneIndex3 >= ComputeShaderConstants.BASE_BONE_COUNT) bw.boneIndex3 += springBoneOffset;

totalSkinIn[vertexCounter + i] = bw;
}
}

List<Vector3> verticesPool = VECTOR3_POOL.Get();
mesh.GetVertices(verticesPool);
NativeArray<Vector3>.Copy(UnsafeUtility.As<List<Vector3>, ListPrivateFieldAccess<Vector3>>(ref verticesPool)._items, 0, totalVertsIn, vertexCounter, currentMeshVertexCount);
Expand All @@ -103,7 +152,7 @@ public void CopyAllBuffers(Mesh mesh, int currentMeshVertexCount, int vertexCoun

//Setup vertex index for current wearable
for (var i = 0; i < mesh.vertexCount; i++)
bindPosesIndexList[vertexCounter + i] = ComputeShaderConstants.BONE_COUNT * skinnedMeshCounter;
bindPosesIndexList[vertexCounter + i] = boneCount * skinnedMeshCounter;
}

//Helper class to access private fields of List<T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public class ComputeShaderConstants
public const string SKINNING_KERNEL_NAME = "main";

//Compute shader properties
public const int BONE_COUNT = 62;
public const int BASE_BONE_COUNT = 62;
public const int MAX_BONE_COUNT = 256;
public static readonly int VERT_COUNT_ID = Shader.PropertyToID("g_VertCount");
public static readonly int LAST_AVATAR_VERT_COUNT_ID = Shader.PropertyToID("_lastAvatarVertCount");
public static readonly int LAST_WEARABLE_VERT_COUNT_ID = Shader.PropertyToID("_lastWearableVertCount");
Expand Down
Loading
Loading