Skip to content

Commit

Permalink
Implement material pool to improve scene loading perf
Browse files Browse the repository at this point in the history
  • Loading branch information
0x7c13 committed Aug 16, 2023
1 parent bd39ad5 commit ef2710f
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 100 deletions.
15 changes: 10 additions & 5 deletions Assets/Scripts/Core/Renderer/StaticMeshRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public sealed class StaticMeshRenderer : MonoBehaviour, IDisposable
private MeshRenderer _meshRenderer;
private MeshFilter _meshFilter;

private Material[] _materials;

public Mesh Render(ref Vector3[] vertices,
ref int[] triangles,
ref Vector3[] normals,
Expand All @@ -23,6 +25,7 @@ public Mesh Render(ref Vector3[] vertices,
{
Dispose();

_materials = materials;
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
_meshRenderer.sharedMaterials = materials;

Expand Down Expand Up @@ -73,6 +76,11 @@ public Bounds GetMeshBounds()
return _meshFilter.sharedMesh.bounds;
}

public Material[] GetMaterials()
{
return _materials;
}

private void OnDisable()
{
Dispose();
Expand All @@ -88,13 +96,10 @@ public void Dispose()

if (_meshRenderer != null)
{
foreach (Material material in _meshRenderer.sharedMaterials)
{
Destroy(material);
}

Destroy(_meshRenderer);
}

_materials = null;
}
}
}
20 changes: 11 additions & 9 deletions Assets/Scripts/Pal3/GameResourceInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ public sealed class GameResourceInitializer : MonoBehaviour
[SerializeField] private TextMeshProUGUI loadingText;

// Optional materials that are used in the game but not open sourced
private Material _toonDefaultMaterial;
private Material _toonOpaqueMaterial;
private Material _toonTransparentMaterial;

private IEnumerator Start()
{
loadingText.text = "正在加载游戏数据...";
yield return null; // Wait for next frame to make sure the text is updated

ResourceRequest toonDefaultMaterialLoadRequest = Resources.LoadAsync<Material>("Materials/ToonDefault");
yield return toonDefaultMaterialLoadRequest;
ResourceRequest toonOpaqueMaterialLoadRequest = Resources.LoadAsync<Material>("Materials/ToonDefault");
yield return toonOpaqueMaterialLoadRequest;

if (toonDefaultMaterialLoadRequest.asset != null)
if (toonOpaqueMaterialLoadRequest.asset != null)
{
_toonDefaultMaterial = toonDefaultMaterialLoadRequest.asset as Material;
_toonOpaqueMaterial = toonOpaqueMaterialLoadRequest.asset as Material;
}

ResourceRequest toonTransparentMaterialLoadRequest = Resources.LoadAsync<Material>("Materials/ToonTransparent");
Expand All @@ -76,7 +76,7 @@ private IEnumerator InitResourceAsync()
ServiceLocator.Instance.Register<Crc32Hash>(crcHash);

// If toon materials are not present, it's an open source build
bool isOpenSourceVersion = _toonDefaultMaterial == null || _toonTransparentMaterial == null;
bool isOpenSourceVersion = _toonOpaqueMaterial == null || _toonTransparentMaterial == null;

// Init settings store
ITransactionalKeyValueStore settingsStore = new PlayerPrefsStore();
Expand Down Expand Up @@ -171,12 +171,14 @@ private IEnumerator InitResourceAsync()

// Init material factories
IMaterialFactory unlitMaterialFactory = new UnlitMaterialFactory();
IMaterialFactory litMaterialFactory = null;
unlitMaterialFactory.PreAllocateMaterialPool(); // Pre-allocate material pool for unlit materials

IMaterialFactory litMaterialFactory = null;
// Only create litMaterialFactory when toon materials are present
if (_toonDefaultMaterial != null && _toonTransparentMaterial != null)
if (_toonOpaqueMaterial != null && _toonTransparentMaterial != null)
{
litMaterialFactory = new LitMaterialFactory(_toonDefaultMaterial, _toonTransparentMaterial);
litMaterialFactory = new LitMaterialFactory(_toonOpaqueMaterial, _toonTransparentMaterial);
litMaterialFactory.PreAllocateMaterialPool(); // Pre-allocate material pool for lit materials
}

// Init Game resource provider
Expand Down
13 changes: 7 additions & 6 deletions Assets/Scripts/Pal3/Renderer/CvdModelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ public class CvdModelRenderer : MonoBehaviour, IDisposable
private float _animationDuration;
private CancellationTokenSource _animationCts = new ();

private IMaterialFactory _materialFactory;

public void Init(CvdFile cvdFile,
ITextureResourceProvider textureProvider,
IMaterialFactory materialFactory,
Color? tintColor = default,
float initTime = 0f)
{
_materialFactory = materialFactory;
_animationDuration = cvdFile.AnimationDuration;
_tintColor = tintColor ?? Color.white;
_currentTime = initTime;
Expand All @@ -55,7 +58,6 @@ public void Init(CvdFile cvdFile,
hashKey,
cvdFile.RootNodes[i],
_textureCache,
materialFactory,
root);
}

Expand Down Expand Up @@ -193,7 +195,6 @@ private void RenderMeshInternal(
string meshName,
CvdGeometryNode node,
Dictionary<string, Texture2D> textureCache,
IMaterialFactory materialFactory,
GameObject parent)
{
var meshObject = new GameObject(meshName);
Expand Down Expand Up @@ -246,7 +247,7 @@ private void RenderMeshInternal(
materialInfoPresenter.material = meshSection.Material;
#endif

Material[] materials = materialFactory.CreateStandardMaterials(
Material[] materials = _materialFactory.CreateStandardMaterials(
RendererType.Cvd,
(textureName, textureCache[textureName]),
shadowTexture: (null, null), // CVD models don't have shadow textures
Expand Down Expand Up @@ -287,7 +288,6 @@ private void RenderMeshInternal(
childMeshName,
node.Children[i],
textureCache,
materialFactory,
meshObject);
}
}
Expand Down Expand Up @@ -353,8 +353,8 @@ private void UpdateMesh(float time)
influence,
frameMatrix);

renderMeshComponent.Mesh.vertices = meshDataBuffer.VertexBuffer;
renderMeshComponent.Mesh.uv = meshDataBuffer.UvBuffer;
renderMeshComponent.Mesh.SetVertices(meshDataBuffer.VertexBuffer);
renderMeshComponent.Mesh.SetUVs(0, meshDataBuffer.UvBuffer);
renderMeshComponent.Mesh.RecalculateBounds();
}
}
Expand Down Expand Up @@ -578,6 +578,7 @@ public void Dispose()

foreach (StaticMeshRenderer meshRenderer in GetComponentsInChildren<StaticMeshRenderer>())
{
_materialFactory.ReturnToPool(meshRenderer.GetMaterials());
Destroy(meshRenderer.gameObject);
}
}
Expand Down
12 changes: 12 additions & 0 deletions Assets/Scripts/Pal3/Renderer/IMaterialFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,17 @@ public Material CreateWaterMaterial(
public void UpdateMaterial(Material material,
Texture2D newMainTexture,
GameBoxBlendFlag blendFlag);

/// <summary>
/// It is very costly to create materials in runtime,
/// this is the interface to pre-allocate a pool of materials.
/// </summary>
public void PreAllocateMaterialPool();

/// <summary>
/// Return materials to the pool.
/// </summary>
/// <param name="materials">Materials to return</param>
public void ReturnToPool(Material[] materials);
}
}
141 changes: 128 additions & 13 deletions Assets/Scripts/Pal3/Renderer/LitMaterialFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace Pal3.Renderer
{
using System;
using System.Collections.Generic;
using Core.GameBox;
using UnityEngine;
using UnityEngine.Rendering;
Expand All @@ -23,18 +25,73 @@ public class LitMaterialFactory : MaterialFactoryBase, IMaterialFactory
private static readonly int OpacityPropertyId = Shader.PropertyToID("_Opacity");
private static readonly int EnvironmentalLightingIntensityPropertyId = Shader.PropertyToID("_EnvironmentalLightingIntensity");

private readonly Material _toonDefaultMaterial;
private readonly Material _toonOpaqueMaterial;
private readonly Material _toonTransparentMaterial;

private const string OPAQUE_MATERIAL_NAME = "LitOpaqueMaterial";
private const string TRANSPARENT_MATERIAL_NAME = "LitTransparentMaterial";

private const int OPAQUE_MATERIAL_POOL_SIZE = 5000;
private const int TRANSPARENT_MATERIAL_POOL_SIZE = 1000;

private readonly Stack<Material> _opaqueMaterialPool = new (OPAQUE_MATERIAL_POOL_SIZE);
private readonly Stack<Material> _transparentMaterialPool = new (TRANSPARENT_MATERIAL_POOL_SIZE);

private readonly float _opaqueMaterialCutoutPropertyDefaultValue;
private readonly float _opaqueMaterialLightIntensityPropertyDefaultValue;
private readonly float _opaqueMaterialEnvironmentalLightingIntensityPropertyDefaultValue;

private readonly int _transparentMaterialBlendSrcFactorPropertyDefaultValue;
private readonly int _transparentMaterialBlendDstFactorPropertyDefaultValue;
private readonly float _transparentMaterialOpacityPropertyDefaultValue;
private readonly float _transparentMaterialLightIntensityPropertyDefaultValue;
private readonly float _transparentMaterialEnvironmentalLightingIntensityPropertyDefaultValue;

public LitMaterialFactory(
Material toonDefaultMaterial,
Material toonOpaqueMaterial,
Material toonTransparentMaterial)
{
_toonDefaultMaterial = toonDefaultMaterial;
_toonOpaqueMaterial = toonOpaqueMaterial;
_toonTransparentMaterial = toonTransparentMaterial;

_opaqueMaterialCutoutPropertyDefaultValue =
_toonOpaqueMaterial.GetFloat(CutoutPropertyId);
_opaqueMaterialLightIntensityPropertyDefaultValue =
_toonOpaqueMaterial.GetFloat(LightIntensityPropertyId);
_opaqueMaterialEnvironmentalLightingIntensityPropertyDefaultValue =
_toonOpaqueMaterial.GetFloat(EnvironmentalLightingIntensityPropertyId);

_transparentMaterialBlendSrcFactorPropertyDefaultValue =
_toonTransparentMaterial.GetInt(BlendSrcFactorPropertyId);
_transparentMaterialBlendDstFactorPropertyDefaultValue =
_toonTransparentMaterial.GetInt(BlendDstFactorPropertyId);
_transparentMaterialOpacityPropertyDefaultValue = _toonTransparentMaterial.GetFloat(OpacityPropertyId);
_transparentMaterialLightIntensityPropertyDefaultValue =
_toonTransparentMaterial.GetFloat(LightIntensityPropertyId);
_transparentMaterialEnvironmentalLightingIntensityPropertyDefaultValue =
_toonTransparentMaterial.GetFloat(EnvironmentalLightingIntensityPropertyId);
}

public void PreAllocateMaterialPool()
{
for (var i = 0; i < OPAQUE_MATERIAL_POOL_SIZE; i++)
{
_opaqueMaterialPool.Push(new Material(_toonOpaqueMaterial)
{
name = OPAQUE_MATERIAL_NAME
});
}

for (var i = 0; i < TRANSPARENT_MATERIAL_POOL_SIZE; i++)
{
_transparentMaterialPool.Push(new Material(_toonTransparentMaterial)
{
name = TRANSPARENT_MATERIAL_NAME
});
}
}

public MaterialShaderType ShaderType { get; } = MaterialShaderType.Lit;
public MaterialShaderType ShaderType => MaterialShaderType.Lit;

/// <inheritdoc/>
public Material[] CreateStandardMaterials(
Expand All @@ -52,9 +109,12 @@ public Material[] CreateStandardMaterials(
// These are the texture/materials that have to be transparent in the game
// m23-3-05.tga => transparent doom like structure in PAL3 scene M23-3
// Q08QN10-05.tga => transparent coffin in PAL3 scene Q08-QN10
// TODO: Add more if needed
bool useTransparentMaterial = mainTexture.name is "m23-3-05.tga" or "Q08QN10-05.tga" ||
blendFlag == GameBoxBlendFlag.InvertColorBlend;
// m25-2-05.tga => transparent roof in PAL3 scene M25-2
// TODO: Add more if needed, by default we use opaque material
bool useTransparentMaterial = blendFlag == GameBoxBlendFlag.InvertColorBlend ||
mainTexture.name.Equals("m23-3-05.tga", StringComparison.OrdinalIgnoreCase) ||
mainTexture.name.Equals("Q08QN10-05.tga", StringComparison.OrdinalIgnoreCase) ||
mainTexture.name.Equals("m25-2-05.tga", StringComparison.OrdinalIgnoreCase);
#elif PAL3A
bool useTransparentMaterial = blendFlag == GameBoxBlendFlag.InvertColorBlend;
#endif
Expand Down Expand Up @@ -140,16 +200,71 @@ public Material CreateWaterMaterial(

private Material CreateBaseTransparentMaterial((string name, Texture2D texture) mainTexture)
{
Material material = new Material(_toonTransparentMaterial);
material.mainTexture = mainTexture.texture;
return material;
if (_transparentMaterialPool.Count > 0)
{
Material material = _transparentMaterialPool.Pop();
// Reset material properties
material.SetInt(BlendSrcFactorPropertyId,
_transparentMaterialBlendSrcFactorPropertyDefaultValue);
material.SetInt(BlendDstFactorPropertyId,
_transparentMaterialBlendDstFactorPropertyDefaultValue);
material.SetFloat(OpacityPropertyId,
_transparentMaterialOpacityPropertyDefaultValue);
material.SetFloat(LightIntensityPropertyId,
_transparentMaterialLightIntensityPropertyDefaultValue);
material.SetFloat(EnvironmentalLightingIntensityPropertyId,
_transparentMaterialEnvironmentalLightingIntensityPropertyDefaultValue);
material.mainTexture = mainTexture.texture;
return material;
}

// In case we run out of transparent materials
return new Material(_toonTransparentMaterial)
{
mainTexture = mainTexture.texture,
name = TRANSPARENT_MATERIAL_NAME
};
}

private Material CreateBaseOpaqueMaterial((string name, Texture2D texture) mainTexture)
{
Material material = new Material(_toonDefaultMaterial);
material.mainTexture = mainTexture.texture;
return material;
if (_opaqueMaterialPool.Count > 0)
{
Material material = _opaqueMaterialPool.Pop();
// Reset material properties
material.SetFloat(CutoutPropertyId,
_opaqueMaterialCutoutPropertyDefaultValue);
material.SetFloat(LightIntensityPropertyId,
_opaqueMaterialLightIntensityPropertyDefaultValue);
material.SetFloat(EnvironmentalLightingIntensityPropertyId,
_opaqueMaterialEnvironmentalLightingIntensityPropertyDefaultValue);
material.mainTexture = mainTexture.texture;
return material;
}

// In case we run out of opaque materials
return new Material(_toonOpaqueMaterial)
{
mainTexture = mainTexture.texture,
name = OPAQUE_MATERIAL_NAME
};
}

protected override void ReturnToPool(Material material)
{
switch (material.name)
{
case TRANSPARENT_MATERIAL_NAME:
material.mainTexture = null;
_transparentMaterialPool.Push(material);
break;
case OPAQUE_MATERIAL_NAME:
material.mainTexture = null;
_opaqueMaterialPool.Push(material);
break;
default:
return;
}
}
}
}
14 changes: 14 additions & 0 deletions Assets/Scripts/Pal3/Renderer/MaterialFactoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,19 @@ internal Shader GetShader(string shaderName)

return shader;
}

public void ReturnToPool(Material[] materials)
{
if (materials == null) return;

foreach (Material material in materials)
{
ReturnToPool(material);
}
}

protected virtual void ReturnToPool(Material material)
{
}
}
}
Loading

0 comments on commit ef2710f

Please sign in to comment.