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,412 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using Unity.Assertions;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Transforms;
using FixedStringName = Unity.Collections.FixedString512Bytes;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[TemporaryBakingType]
public struct SkinnedMeshRendererRootBoneEntity: IComponentData
{
public Entity value;
}
[TemporaryBakingType]
public struct SkinnedMeshSplitSubmeshEntities: IComponentData { }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public partial class SkinnedMeshBaker: Baker<SkinnedMeshRenderer>
{
public override unsafe void Bake(SkinnedMeshRenderer a)
{
if (a.sharedMesh == null)
return;
var smrHash = CalculateSkinnedMeshHash(a.sharedMesh);
var isSMSBlobExists = TryGetBlobAssetReference<SkinnedMeshInfoBlob>(smrHash, out var smrBlobAsset);
if (!isSMSBlobExists)
{
smrBlobAsset = CreateSkinnedMeshBlob(a, smrHash);
AddBlobAssetWithCustomHash(ref smrBlobAsset, smrHash);
}
var rbe = new SkinnedMeshRendererRootBoneEntity()
{
value = _State.BakedEntityData->GetEntity(a.rootBone)
};
var smrc = new SkinnedMeshRendererComponent()
{
smrInfoBlob = smrBlobAsset,
animatedRigEntity = GetEntity(a.gameObject.GetComponentInParent<RigDefinitionAuthoring>(true), TransformUsageFlags.Dynamic),
rootBoneIndexInRig = -1,
nameHash = a.name.CalculateHash32()
};
var e = GetEntity(a, TransformUsageFlags.Renderable);
var splitOnSubmeshes = a.GetComponent<SkinnedMeshSplitOnSubmeshEntitiesAuthoring>();
// One or many render entities
if (!splitOnSubmeshes || a.sharedMaterials.Length == 0)
{
AddEntityComponents(e, a, smrc, rbe, -1);
}
else
{
// In case of submesh splitting requested create one entity per material in source skinned mesh renderer and configure it as ordinary
// skinned mesh entity
var additionalEntities = new NativeArray<Entity>(a.sharedMaterials.Length - 1, Allocator.Temp);
CreateAdditionalEntities(additionalEntities, TransformUsageFlags.ManualOverride);
var parent = new Parent() { Value = GetEntity(a.transform.parent, TransformUsageFlags.Dynamic) };
var lt = new LocalTransform() { Position = a.transform.localPosition, Rotation = a.transform.localRotation, Scale = a.transform.localScale.x };
// Original SMR with first material
AddEntityComponents(e, a, smrc, rbe, 0);
for (var i = 0; i < additionalEntities.Length; ++i)
{
var materialIndex = i + 1;
var renderEntity = additionalEntities[i];
if (parent.Value != Entity.Null)
AddComponent(renderEntity, parent);
AddComponent(renderEntity, lt);
AddComponent<LocalToWorld>(renderEntity);
AddEntityComponents(renderEntity, a, smrc, rbe, materialIndex);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void AddEntityComponents(Entity e, SkinnedMeshRenderer a, in SkinnedMeshRendererComponent smrc, SkinnedMeshRendererRootBoneEntity smrrbe, int materialIndex)
{
AddComponent(e, smrrbe);
AddComponent(e, smrc);
if (a.updateWhenOffscreen)
{
AddComponent<ShouldUpdateBoundingBoxTag>(e);
}
CheckMaterialCompatibility(a);
CreateRenderComponents(e, a, materialIndex);
CreateSkinMatricesBuffer(e, a);
CreateBlendShapeWeightsBuffer(e, a);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Hash128 CalculateSkinnedMeshHash(Mesh m)
{
var rv = new Hash128();
#if UNITY_EDITOR
var assetId = BakingUtils.GetAssetID(m);
rv = new Hash128(assetId.x, assetId.y, 0, 0);
#endif
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateSkinnedMeshBonesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r)
{
var bonesArr = bb.Allocate(ref smrBlob.bones, r.bones.Length);
for (int j = 0; j < bonesArr.Length; ++j)
{
var b = r.bones[j];
ref var boneBlob = ref bonesArr[j];
if (b != null)
{
#if RUKHANKA_DEBUG_INFO
bb.AllocateString(ref boneBlob.name, b.name);
#endif
var bn = new FixedStringName(b.name);
boneBlob.hash = bn.CalculateHash32();
boneBlob.bindPose = r.sharedMesh.bindposes[j];
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateBlendShapesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r)
{
var blendShapeHashesArr = bb.Allocate(ref smrBlob.blendShapes, r.sharedMesh.blendShapeCount);
for (var j = 0; j < blendShapeHashesArr.Length; ++j)
{
ref var bs = ref blendShapeHashesArr[j];
var bsName = "blendShape." + r.sharedMesh.GetBlendShapeName(j);
bs.hash = bsName.CalculateHash32();
#if RUKHANKA_DEBUG_INFO
if (bsName.Length > 0)
bb.AllocateString(ref bs.name, bsName);
#endif
}
smrBlob.meshBlendShapesCount = r.sharedMesh.blendShapeCount;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateBoneWeightsDataBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r)
{
CreateBoneWeightsIndicesBlob(ref bb, ref smrBlob, r);
smrBlob.meshBoneWeightsCount = r.sharedMesh.GetAllBoneWeights().Length;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateBoneWeightsIndicesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r)
{
var mesh = r.sharedMesh;
var allBoneWeights = mesh.GetBonesPerVertex();
using var outArr = new NativeArray<uint>(allBoneWeights.Length, Allocator.TempJob);
var computeAbsoluteOffsetsJob = new ComputeAbsoluteBoneWeightsIndicesOffsetsJob()
{
bonesPerVertex = allBoneWeights,
outIndicesArr = outArr
};
computeAbsoluteOffsetsJob.Run();
var ba = bb.Allocate(ref smrBlob.boneWeightsIndices, allBoneWeights.Length);
//UnsafeUtility.MemCpy(ba.GetUnsafePtr(), allBoneWeights.GetUnsafeReadOnlyPtr(), allBoneWeights.Length * UnsafeUtility.SizeOf<uint>());
for (var i = 0; i < ba.Length; ++i)
{
ba[i] = outArr[i];
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BlobAssetReference<SkinnedMeshInfoBlob> CreateSkinnedMeshBlob(SkinnedMeshRenderer r, Hash128 smrHash)
{
var bb = new BlobBuilder(Allocator.Temp);
ref var smrBlob = ref bb.ConstructRoot<SkinnedMeshInfoBlob>();
smrBlob.hash = smrHash;
#if RUKHANKA_DEBUG_INFO
if (r.name.Length > 0)
bb.AllocateString(ref smrBlob.skeletonName, r.name);
var startTimeMarker = Time.realtimeSinceStartup;
#endif
CreateSkinnedMeshBonesBlob(ref bb, ref smrBlob, r);
CreateBlendShapesBlob(ref bb, ref smrBlob, r);
CreateBoneWeightsDataBlob(ref bb, ref smrBlob, r);
smrBlob.meshVerticesCount = r.sharedMesh.vertexCount;
#if RUKHANKA_DEBUG_INFO
var dt = Time.realtimeSinceStartupAsDouble - startTimeMarker;
smrBlob.bakingTime = (float)dt;
#endif
var rv = bb.CreateBlobAssetReference<SkinnedMeshInfoBlob>(Allocator.Persistent);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateRenderComponents(Entity renderEntity, SkinnedMeshRenderer a, int materialIndex)
{
var mmIndices = new MaterialMeshIndex[a.sharedMaterials.Length];
for (var i = 0; i < mmIndices.Length; ++i)
{
mmIndices[i] = new () { MeshIndex = 0, MaterialIndex = i, SubMeshIndex = i };
}
var rma = new RenderMeshArray(a.sharedMaterials, new [] { a.sharedMesh }, mmIndices);
var rmd = new RenderMeshDescription(a);
var materialBaseIndex = math.select(0, materialIndex, materialIndex >= 0);
var materialsCount = math.select(a.sharedMaterials.Length, 1, materialIndex >= 0);
var mmi = MaterialMeshInfo.FromMaterialMeshIndexRange(materialBaseIndex, materialsCount);
var materialList = new ReadOnlySpan<Material>(rma.GetMaterials(mmi).ToArray());
var componentFlags = RenderMeshUtility.EntitiesGraphicsComponentFlags.UseRenderMeshArray;
componentFlags.AppendMotionAndProbeFlags(rmd, false);
componentFlags.AppendPerVertexMotionPassFlag(materialList);
componentFlags.AppendDepthSortedFlag(rma.GetMaterials(mmi));
AddComponent(renderEntity, RenderMeshUtility.ComputeComponentTypes(componentFlags));
GetLayer(a);
SetSharedComponent(renderEntity, rmd.FilterSettings);
SetSharedComponentManaged(renderEntity, rma);
SetComponent(renderEntity, mmi);
AddComponent<DeformedMeshIndex>(renderEntity);
AddMeshRendererBakingData(renderEntity, a);
AddLODComponents(renderEntity, a);
var rbb = a.localBounds.ToAABB();
if (a.rootBone != null)
{
var boundsTransform = math.mul(a.transform.worldToLocalMatrix, a.rootBone.localToWorldMatrix);
rbb = AABB.Transform(boundsTransform, rbb);
}
SetComponent(renderEntity, new RenderBounds { Value = rbb });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void AddLODComponents(Entity renderEntity, SkinnedMeshRenderer a)
{
var lodGroup = a.GetComponentInParent<LODGroup>(true);
var lge = GetEntity(lodGroup, TransformUsageFlags.Renderable);
var lodMask = GetLODMask(lodGroup, a);
if (lge == Entity.Null || lodMask == -1)
return;
var mlc = new MeshLODComponent () { Group = lge, LODMask = lodMask };
AddComponent(renderEntity, mlc);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int GetLODMask(LODGroup lodGroup, SkinnedMeshRenderer a)
{
if (lodGroup == null)
return -1;
var lods = lodGroup.GetLODs();
int lodGroupMask = 0;
// Find the renderer inside the LODGroup
for (int i = 0; i < lods.Length; ++i)
{
foreach (var renderer in lods[i].renderers)
{
if (renderer == a)
{
lodGroupMask |= (1 << i);
}
}
}
return lodGroupMask > 0 ? lodGroupMask : -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void AddMeshRendererBakingData(Entity renderEntity, SkinnedMeshRenderer a)
{
var entitiesGraphicsSystemType = typeof(EntitiesGraphicsSystem);
var meshRendererBakingDataTypeName = "Unity.Rendering.MeshRendererBakingData";
var meshRendererBakingDataType = entitiesGraphicsSystemType.Assembly.GetType(meshRendererBakingDataTypeName);
var mrbdTypeIndex = TypeManager.GetTypeIndex(meshRendererBakingDataType);
var typeInfo = TypeManager.GetTypeInfo(mrbdTypeIndex);
var untypedComponentData = UnsafeUtility.Malloc(typeInfo.TypeSize, typeInfo.AlignmentInBytes, Allocator.Temp);
UnityObjectRef<Renderer> meshRenderer = a;
UnsafeUtility.MemCpy(untypedComponentData, &meshRenderer, typeInfo.TypeSize);
UnsafeAddComponent(renderEntity, mrbdTypeIndex, typeInfo.TypeSize, untypedComponentData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateSkinMatricesBuffer(Entity e, SkinnedMeshRenderer a)
{
DependsOn(a.transform);
var mesh = a.sharedMesh;
var boneWeights = mesh.GetAllBoneWeights();
var bindPoses = mesh.GetBindposes();
var bones = a.bones;
Assert.IsTrue(bones.Length == bindPoses.Length);
var hasSkinning = boneWeights.Length > 0 && bindPoses.Length > 0;
if (!hasSkinning)
return;
var ownInverseTransform = a.worldToLocalMatrix;
var skinMatrixBuf = AddBuffer<Rukhanka.SkinMatrix>(e);
skinMatrixBuf.Resize(bindPoses.Length, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < bones.Length; ++i)
{
var b = bones[i];
if (b == null)
continue;
DependsOn(b);
var bp = bindPoses[i];
var boneMatRootSpace = math.mul(ownInverseTransform, b.localToWorldMatrix);
var skinMatRootSpace = math.mul(boneMatRootSpace, bp);
var sm = new Rukhanka.SkinMatrix()
{
Value = new float3x4(skinMatRootSpace.c0.xyz, skinMatRootSpace.c1.xyz, skinMatRootSpace.c2.xyz, skinMatRootSpace.c3.xyz)
};
skinMatrixBuf[i] = sm;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateBlendShapeWeightsBuffer(Entity e, SkinnedMeshRenderer a)
{
var mesh = a.sharedMesh;
if (mesh.blendShapeCount == 0)
return;
var bswb = AddBuffer<Rukhanka.BlendShapeWeight>(e);
for (var i = 0; i < mesh.blendShapeCount; ++i)
{
var srcBlendShapeWeight = a.GetBlendShapeWeight(i);
var bsw = new Rukhanka.BlendShapeWeight()
{
Value = srcBlendShapeWeight
};
bswb.Add(bsw);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CheckMaterialCompatibility(SkinnedMeshRenderer a)
{
var materials = new List<Material>();
a.GetSharedMaterials(materials);
for (var i = 0; i < materials.Count; ++i)
{
var m = materials[i];
if (m == null)
continue;
#if RUKHANKA_ENABLE_DEFORMATION_MOTION_VECTORS
var deformationCompatibleShader = m.HasProperty("_DeformationParamsForMotionVectors");
#else
var deformationCompatibleShader = m.HasProperty("_DeformedMeshIndex");
#endif
if (!deformationCompatibleShader)
{
var s = $"Shader [{m.shader.name}] on [{a.name}] does not support skinning. This can result in incorrect rendering."
+ " Please see the <a href=\"https://docs.rukhanka.com/shaders_with_deformations\">documentation</a>"
+ " for information about making a deformation-compatible shader";
Debug.LogWarning(s, a);
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e3174395e24a38d44888c9548c3c4b50
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.Hybrid/SkinnedMesh/SkinnedMeshBaker.cs
uploadId: 897522
@@ -0,0 +1,33 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public partial class SkinnedMeshBaker
{
[BurstCompile]
struct ComputeAbsoluteBoneWeightsIndicesOffsetsJob: IJob
{
[ReadOnly]
public NativeArray<byte> bonesPerVertex;
public NativeArray<uint> outIndicesArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
var absOffset = 0u;
for (var i = 0; i < bonesPerVertex.Length; ++i)
{
var bpv = bonesPerVertex[i];
var v = SkinnedMeshInfoBlob.PackBoneCountAndOffset(bpv, absOffset);
outIndicesArr[i] = v;
absOffset += bpv;
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9502de1e2111c5145a7258293fc3f4d7
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.Hybrid/SkinnedMesh/SkinnedMeshBaker_Jobs.cs
uploadId: 897522
@@ -0,0 +1,56 @@
using Unity;
using Unity.Collections;
using Unity.Entities;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
[UpdateInGroup(typeof(PostBakingSystemGroup))]
public partial class SkinnedMeshConversionSystem : SystemBase
{
protected override unsafe void OnUpdate()
{
var actualizeSkinnedMeshDataJob = new ActualizeSkinnedMeshDataJob()
{
animEntityRefLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true),
};
actualizeSkinnedMeshDataJob.ScheduleParallel();
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (rma, e) in SystemAPI.Query<RenderMeshArray>().WithEntityAccess().WithAll<SkinnedMeshSplitSubmeshEntities>())
{
var eg = EntityManager.GetComponentData<EntityGuid>(e);
for (var i = 0; i < rma.MaterialMeshIndices.Length; i++)
{
var originalSMR = i == 0;
// Modify original skinned mesh renderer to draw only first submesh
var smrEntity = originalSMR ? e : ecb.Instantiate(e);
var mmiForSubmesh = MaterialMeshInfo.FromMaterialMeshIndexRange(i, 1);
mmiForSubmesh.Material = MaterialMeshInfo.ArrayIndexToStaticIndex(i);
mmiForSubmesh.Mesh = MaterialMeshInfo.ArrayIndexToStaticIndex(0);
ecb.SetComponent(smrEntity, mmiForSubmesh);
if (!originalSMR)
{
// Modify EntityGuid to prevent 'duplicated GUID' exceptions)
#if UNITY_6000_5_OR_NEWER
var osei = (int)eg.OriginatingSubEntityId.GetRawData() + 1;
eg.OriginatingSubEntityId = *(EntityId*)&osei;
#else
eg.a += 1;
#endif
ecb.SetComponent(smrEntity, eg);
}
ecb.RemoveComponent<SkinnedMeshSplitSubmeshEntities>(smrEntity);
}
}
ecb.Playback(EntityManager);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d16c748b05bbae747a8e6beeb8ae4acc
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.Hybrid/SkinnedMesh/SkinnedMeshConversionSystem.cs
uploadId: 897522
@@ -0,0 +1,34 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public partial class SkinnedMeshConversionSystem
{
//=================================================================================================================//
[BurstCompile]
[WithOptions(EntityQueryOptions.IncludePrefab)]
partial struct ActualizeSkinnedMeshDataJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> animEntityRefLookup;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(Entity e, ref SkinnedMeshRendererComponent asmc, in SkinnedMeshRendererRootBoneEntity rbe, in LocalTransform lt)
{
if (animEntityRefLookup.HasComponent(rbe.value))
{
var are = animEntityRefLookup[rbe.value];
asmc.rootBoneIndexInRig = are.boneIndexInAnimationRig;
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 26d5f430d5861054297da994bfcd187f
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.Hybrid/SkinnedMesh/SkinnedMeshConversionSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,8 @@
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
class SkinnedMeshSplitOnSubmeshEntitiesAuthoring: MonoBehaviour { }
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 85397f545e9af0d46a662a904602b3dd
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.Hybrid/SkinnedMesh/SkinnedMeshSplitOnSubmeshEntitiesAuthoring.cs
uploadId: 897522