Skip to content

Commit

Permalink
Reduce heap allocation when switching mv3 animations
Browse files Browse the repository at this point in the history
  • Loading branch information
0x7c13 committed Oct 13, 2023
1 parent 730fb70 commit b41c94e
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 64 deletions.
56 changes: 52 additions & 4 deletions Assets/Scripts/Engine/Extensions/UnityPrimitivesConvertor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ public static UnityEngine.Vector3[] ToUnityPositions(this GameBoxVector3[] gameB
return unityPositions;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToUnityPositionsNonAlloc(this GameBoxVector3[] gameBoxPositions,
UnityEngine.Vector3[] unityPositions, float scale = GameBoxUnitToUnityUnit)
{
if (gameBoxPositions == null || unityPositions == null || unityPositions.Length != gameBoxPositions.Length)
{
throw new ArgumentException("unityPositions and gameBoxPositions must be non-null and have the same length");
}

for (var i = 0; i < gameBoxPositions.Length; i++)
{
unityPositions[i] = ToUnityVector3(gameBoxPositions[i], scale);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ToUnityDistance(this float gameBoxDistance)
{
Expand Down Expand Up @@ -142,6 +157,21 @@ public static UnityEngine.Vector3[] ToUnityNormals(this GameBoxVector3[] gameBox
return unityNormals;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToUnityNormalsNonAlloc(this GameBoxVector3[] gameBoxNormals,
UnityEngine.Vector3[] unityNormals)
{
if (gameBoxNormals == null || unityNormals == null || gameBoxNormals.Length != unityNormals.Length)
{
throw new ArgumentException("gameBoxNormals and unityNormals must be non-null and have the same length");
}

for (var i = 0; i < gameBoxNormals.Length; i++)
{
unityNormals[i] = ToUnityNormal(gameBoxNormals[i]);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnityEngine.Quaternion CvdQuaternionToUnityQuaternion(this GameBoxQuaternion cvdGameBoxQuaternion)
{
Expand Down Expand Up @@ -226,6 +256,20 @@ public static int[] ToUnityTriangles(this int[] gameBoxTriangles)
return unityTriangles;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToUnityTrianglesNonAlloc(this int[] gameBoxTriangles, int[] unityTriangles)
{
if (gameBoxTriangles == null || unityTriangles == null || gameBoxTriangles.Length != unityTriangles.Length)
{
throw new ArgumentException("gameBoxTriangles and unityTriangles must be non-null and have the same length");
}

for (var i = 0; i < gameBoxTriangles.Length; i++)
{
unityTriangles[i] = gameBoxTriangles[gameBoxTriangles.Length - 1 - i];
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnityEngine.Matrix4x4 ToUnityMatrix4x4(this GameBoxMatrix4x4 gameBoxMatrix)
{
Expand Down Expand Up @@ -268,12 +312,16 @@ public static UnityEngine.Vector2[] ToUnityVector2s(this GameBoxVector2[] gameBo
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe UnityEngine.Vector2[] ToUnityVector2sUnsafe(this GameBoxVector2[] gameBoxVector2s)
public static void ToUnityVector2sNonAlloc(this GameBoxVector2[] gameBoxVector2s, UnityEngine.Vector2[] unityVector2s)
{
if (gameBoxVector2s == null) return null;
fixed (GameBoxVector2* srcPtr = gameBoxVector2s)
if (gameBoxVector2s == null || unityVector2s == null || gameBoxVector2s.Length != unityVector2s.Length)
{
return new Span<UnityEngine.Vector2>(srcPtr, gameBoxVector2s.Length).ToArray();
throw new ArgumentException("gameBoxVector2s and unityVector2s must be non-null and have the same length");
}

for (var i = 0; i < gameBoxVector2s.Length; i++)
{
unityVector2s[i] = gameBoxVector2s[i].ToUnityVector2();
}
}

Expand Down
35 changes: 26 additions & 9 deletions Assets/Scripts/Engine/Renderer/StaticMeshRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public sealed class StaticMeshRenderer : GameEntityScript, IDisposable
{
private MeshRenderer _meshRenderer;
private MeshFilter _meshFilter;

private Material[] _materials;

protected override void OnDisableGameEntity()
Expand All @@ -25,8 +24,8 @@ protected override void OnDisableGameEntity()
public Mesh Render(Vector3[] vertices,
int[] triangles,
Vector3[] normals,
Vector2[] mainTextureUvs,
Vector2[] secondaryTextureUvs,
(int channel, Vector2[] uvs) mainTextureUvs,
(int channel, Vector2[] uvs) secondaryTextureUvs,
Material[] materials,
bool isDynamic)
{
Expand All @@ -38,19 +37,37 @@ public Mesh Render(Vector3[] vertices,
_meshRenderer.receiveShadows = _receiveShadows;

_meshFilter = GameEntity.AddComponent<MeshFilter>();
var mesh = new Mesh();

Mesh mesh = new();

if (isDynamic)
{
mesh.MarkDynamic();
}

mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetNormals(normals);
mesh.SetUVs(0, mainTextureUvs);
mesh.SetUVs(1, secondaryTextureUvs);

if (triangles.Length == 0)
if (triangles != null)
{
mesh.SetTriangles(triangles, 0);
}

if (normals != null) // normals can be null
{
mesh.SetNormals(normals);
}

if (mainTextureUvs != default)
{
mesh.SetUVs(mainTextureUvs.channel, mainTextureUvs.uvs);
}

if (secondaryTextureUvs != default)
{
mesh.SetUVs(secondaryTextureUvs.channel, secondaryTextureUvs.uvs);
}

if (triangles == null)
{
// https://docs.unity3d.com/ScriptReference/Mesh.RecalculateBounds.html
// After modifying vertices you should call this function to ensure the
Expand Down
1 change: 1 addition & 0 deletions Assets/Scripts/Pal3.Game/Rendering/MeshDataBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public class MeshDataBuffer
public Vector3[] VertexBuffer;
public Vector3[] NormalBuffer;
public Vector2[] UvBuffer;
public int[] TriangleBuffer;
}
}
32 changes: 18 additions & 14 deletions Assets/Scripts/Pal3.Game/Rendering/Renderer/CvdModelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,14 @@ private void RenderMeshInternal(
{
CvdMeshSection meshSection = node.Mesh.MeshSections[i];

var sectionHashKey = $"{meshName}_{i}";
string sectionHashKey = $"{meshName}_{i}";

var meshDataBuffer = new MeshDataBuffer
MeshDataBuffer meshDataBuffer = new()
{
VertexBuffer = new Vector3[meshSection.FrameVertices[frameIndex].Length],
NormalBuffer = new Vector3[meshSection.FrameVertices[frameIndex].Length],
UvBuffer = new Vector2[meshSection.FrameVertices[frameIndex].Length],
TriangleBuffer = meshSection.GameBoxTriangles.ToUnityTriangles()
};

UpdateMeshDataBuffer(ref meshDataBuffer,
Expand All @@ -264,20 +265,20 @@ private void RenderMeshInternal(

Material[] materials = _materialFactory.CreateStandardMaterials(
RendererType.Cvd,
(textureName, textureCache[textureName]),
shadowTexture: (null, null), // CVD models don't have shadow textures
_tintColor,
meshSection.BlendFlag);
mainTexture: (textureName, textureCache[textureName]),
shadowTexture: default, // CVD models don't have shadow textures
tintColor: _tintColor,
blendFlag: meshSection.BlendFlag);

var meshRenderer = meshSectionEntity.AddComponent<StaticMeshRenderer>();
Mesh renderMesh = meshRenderer.Render(
meshDataBuffer.VertexBuffer,
meshSection.GameBoxTriangles.ToUnityTriangles(),
meshDataBuffer.NormalBuffer,
meshDataBuffer.UvBuffer,
meshDataBuffer.UvBuffer,
materials,
true);
vertices: meshDataBuffer.VertexBuffer,
triangles: meshDataBuffer.TriangleBuffer,
normals: meshDataBuffer.NormalBuffer,
mainTextureUvs: (channel: 0, uvs: meshDataBuffer.UvBuffer),
secondaryTextureUvs: default, // CVD models don't have secondary texture
materials: materials,
isDynamic: true);

renderMesh.RecalculateNormals();
renderMesh.RecalculateTangents();
Expand Down Expand Up @@ -582,7 +583,10 @@ private Quaternion GetRotation(float time,

public void StopCurrentAnimation()
{
_animationCts.Cancel();
if (!_animationCts.IsCancellationRequested)
{
_animationCts.Cancel();
}
}

public void Dispose()
Expand Down
56 changes: 45 additions & 11 deletions Assets/Scripts/Pal3.Game/Rendering/Renderer/Mv3ModelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ private void InitSubMeshes(int index,

Material[] materials = _materialFactory.CreateStandardMaterials(
RendererType.Mv3,
(textureName, _textures[index]),
shadowTexture: (null, null), // MV3 models don't have shadow textures
_tintColor,
_textureHasAlphaChannel[index] ? GameBoxBlendFlag.AlphaBlend : GameBoxBlendFlag.Opaque);
mainTexture: (textureName, _textures[index]),
shadowTexture: default, // MV3 models don't have shadow textures
tintColor: _tintColor,
blendFlag: _textureHasAlphaChannel[index] ? GameBoxBlendFlag.AlphaBlend : GameBoxBlendFlag.Opaque);

_materials[index] = materials;

Expand All @@ -244,14 +244,48 @@ private void InitSubMeshes(int index,
_renderMeshComponents[index].MeshDataBuffer.VertexBuffer = new Vector3[mv3Mesh.KeyFrames[0].GameBoxVertices.Length];
}

if (_renderMeshComponents[index].MeshDataBuffer.TriangleBuffer == null ||
_renderMeshComponents[index].MeshDataBuffer.TriangleBuffer.Length !=
mv3Mesh.GameBoxTriangles.Length)
{
_renderMeshComponents[index].MeshDataBuffer.TriangleBuffer = new int[mv3Mesh.GameBoxTriangles.Length];
}

if (_renderMeshComponents[index].MeshDataBuffer.NormalBuffer == null ||
_renderMeshComponents[index].MeshDataBuffer.NormalBuffer.Length !=
mv3Mesh.GameBoxNormals.Length)
{
_renderMeshComponents[index].MeshDataBuffer.NormalBuffer = new Vector3[mv3Mesh.GameBoxNormals.Length];
}

if (_renderMeshComponents[index].MeshDataBuffer.UvBuffer == null ||
_renderMeshComponents[index].MeshDataBuffer.UvBuffer.Length !=
mv3Mesh.Uvs.Length)
{
_renderMeshComponents[index].MeshDataBuffer.UvBuffer = new Vector2[mv3Mesh.Uvs.Length];
}

mv3Mesh.KeyFrames[0].GameBoxVertices.ToUnityPositionsNonAlloc(
_renderMeshComponents[index].MeshDataBuffer.VertexBuffer,
UnityPrimitivesConvertor.GameBoxMv3UnitToUnityUnit);

mv3Mesh.GameBoxTriangles.ToUnityTrianglesNonAlloc(
_renderMeshComponents[index].MeshDataBuffer.TriangleBuffer);

mv3Mesh.GameBoxNormals.ToUnityNormalsNonAlloc(
_renderMeshComponents[index].MeshDataBuffer.NormalBuffer);

mv3Mesh.Uvs.ToUnityVector2sNonAlloc(
_renderMeshComponents[index].MeshDataBuffer.UvBuffer);

Mesh renderMesh = meshRenderer.Render(
mv3Mesh.KeyFrames[0].GameBoxVertices.ToUnityPositions(UnityPrimitivesConvertor.GameBoxMv3UnitToUnityUnit),
mv3Mesh.GameBoxTriangles.ToUnityTriangles(),
mv3Mesh.GameBoxNormals.ToUnityNormals(),
mv3Mesh.Uvs.ToUnityVector2s(),
mv3Mesh.Uvs.ToUnityVector2s(),
_materials[index],
true);
vertices: _renderMeshComponents[index].MeshDataBuffer.VertexBuffer,
triangles: _renderMeshComponents[index].MeshDataBuffer.TriangleBuffer,
normals: _renderMeshComponents[index].MeshDataBuffer.NormalBuffer,
mainTextureUvs: (channel: 0, uvs: _renderMeshComponents[index].MeshDataBuffer.UvBuffer),
secondaryTextureUvs: default, // MV3 models don't have secondary texture
materials: _materials[index],
isDynamic: true);

renderMesh.RecalculateTangents();

Expand Down
27 changes: 14 additions & 13 deletions Assets/Scripts/Pal3.Game/Rendering/Renderer/PolyModelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,18 @@ Material[] CreateMaterials(bool isWaterSurface, int mainTextureIndex, int shadow
}

materials[0] = _materialFactory.CreateWaterMaterial(
textures[mainTextureIndex],
shadowTextureIndex >= 0 ? textures[shadowTextureIndex] : (null, null),
waterSurfaceOpacity,
mainTexture: textures[mainTextureIndex],
shadowTexture: shadowTextureIndex >= 0 ? textures[shadowTextureIndex] : default,
opacity: waterSurfaceOpacity,
blendFlag);
}
else
{
materials = _materialFactory.CreateStandardMaterials(
RendererType.Pol,
textures[mainTextureIndex],
shadowTextureIndex >= 0 ? textures[shadowTextureIndex] : (null, null),
_tintColor,
mainTexture: textures[mainTextureIndex],
shadowTexture: shadowTextureIndex >= 0 ? textures[shadowTextureIndex] : default,
tintColor: _tintColor,
blendFlag);
}
return materials;
Expand All @@ -227,13 +227,14 @@ Material[] CreateMaterials(bool isWaterSurface, int mainTextureIndex, int shadow
StartWaterSurfaceAnimation(materials[0], textures[mainTextureIndex].texture, cancellationToken);
}

_ = meshRenderer.Render(mesh.VertexInfo.GameBoxPositions.ToUnityPositions(),
mesh.Textures[i].GameBoxTriangles.ToUnityTriangles(),
mesh.VertexInfo.GameBoxNormals.ToUnityNormals(),
mesh.VertexInfo.Uvs[mainTextureIndex].ToUnityVector2s(),
mesh.VertexInfo.Uvs[Math.Max(shadowTextureIndex, 0)].ToUnityVector2s(),
materials,
false);
_ = meshRenderer.Render(
vertices: mesh.VertexInfo.GameBoxPositions.ToUnityPositions(),
triangles: mesh.Textures[i].GameBoxTriangles.ToUnityTriangles(),
normals: mesh.VertexInfo.GameBoxNormals.ToUnityNormals(),
mainTextureUvs: (channel: 0, uvs: mesh.VertexInfo.Uvs[mainTextureIndex].ToUnityVector2s()),
secondaryTextureUvs: (channel: 1, uvs: mesh.VertexInfo.Uvs[Math.Max(shadowTextureIndex, 0)].ToUnityVector2s()),
materials: materials,
isDynamic: false);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ private void RenderSubMesh(MshMesh subMesh, int subMeshIndex)

int numOfIndices = subMesh.Faces.Length * 3;
int[] triangles = new int[numOfIndices];
Vector2[] uvs1 = new Vector2[numOfIndices];
Vector2[] uvs = new Vector2[numOfIndices];
int[] indexBuffer = new int[numOfIndices];

var index = 0;
Expand All @@ -358,7 +358,7 @@ private void RenderSubMesh(MshMesh subMesh, int subMeshIndex)
for (var i = 0; i < phyFace.Indices.Length; i++)
{
indexBuffer[index] = phyFace.Indices[i];
uvs1[index] = new Vector2(phyFace.U[i, 0], phyFace.V[i, 0]);
uvs[index] = new Vector2(phyFace.U[i, 0], phyFace.V[i, 0]);
triangles[index] = index;
index++;
}
Expand All @@ -374,20 +374,20 @@ private void RenderSubMesh(MshMesh subMesh, int subMeshIndex)

Material[] materials = _materialFactory.CreateStandardMaterials(
RendererType.Msh,
_mainTexture,
default,
_tintColor,
GameBoxBlendFlag.Opaque);
mainTexture: _mainTexture,
shadowTexture: default,
tintColor: _tintColor,
blendFlag: GameBoxBlendFlag.Opaque);

var meshRenderer = _meshEntities[subMeshIndex].AddComponent<StaticMeshRenderer>();
Mesh renderMesh = meshRenderer.Render(
vertices,
triangles.ToUnityTriangles(),
normals: null,
uvs1,
secondaryTextureUvs: null,
materials,
true);
vertices: vertices,
triangles: triangles.ToUnityTriangles(),
normals: default,
mainTextureUvs: (channel: 0, uvs: uvs),
secondaryTextureUvs: default, // Skeletal model does not have secondary texture
materials: materials,
isDynamic: true);

renderMesh.RecalculateNormals();
renderMesh.RecalculateTangents();
Expand Down

0 comments on commit b41c94e

Please sign in to comment.