542 lines
21 KiB
C#
542 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using Unity.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace Assets.ThoMagic.Renderer
|
|
{
|
|
public static class RendererPool
|
|
{
|
|
public const int RESIZE_COUNT = 100000;
|
|
public const int STREAMOUT_SIZE = 10000;
|
|
|
|
public static bool isDisposed = true;
|
|
private static ComputeShader instancingShader = null;
|
|
private static CommandBuffer commandBuffer = null;
|
|
private static int uploadInstancesShaderId = 0, resizeInstanceBufferShaderId = 0;
|
|
|
|
private static object threadSecurity = new object();
|
|
private static int maxInstanceCount = 0;
|
|
private static int instanceUploadCount = 0;
|
|
private static readonly Dictionary<int, CameraRenderer> cameras = new Dictionary<int, CameraRenderer>();
|
|
public static readonly Dictionary<int, ObjectData> objects = new Dictionary<int, ObjectData>();
|
|
public static readonly Dictionary<int, InstanceStreamer> streamers = new Dictionary<int, InstanceStreamer>();
|
|
public static readonly List<ObjectData> objectsList = new List<ObjectData>();
|
|
public static readonly List<PrefabData> prefabData = new List<PrefabData>();
|
|
|
|
public static NativeArray<InstanceData> uploadInstancesArray;
|
|
|
|
public static readonly Queue<InstanceData> uploadInstancesArrayQueue = new Queue<InstanceData>();
|
|
|
|
public static readonly Queue<uint> prefabDataFreeSlot = new Queue<uint>();
|
|
public static readonly Queue<int> instanceBufferFreeSlot = new Queue<int>();
|
|
|
|
public static ComputeBuffer globalUpdateInstanceBuffer = null;
|
|
public static ComputeBuffer globalInstanceBuffer = null;
|
|
public static ComputeBuffer tempGlobalInstanceBuffer = null;
|
|
|
|
private static Vector3 _origin;
|
|
private static List<int> _keysToRemove = new List<int>();
|
|
|
|
private static bool _instanceCountChanged;
|
|
|
|
public static bool externInstanceCountChanged
|
|
{
|
|
set
|
|
{
|
|
if (value == true)
|
|
{
|
|
foreach (var camera in cameras.Values)
|
|
{
|
|
camera.instanceCountChanged = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool instanceCountChanged
|
|
{
|
|
set
|
|
{
|
|
if (value == true && !_instanceCountChanged)
|
|
{
|
|
_instanceCountChanged = true;
|
|
foreach (var camera in cameras.Values)
|
|
{
|
|
camera.instanceCountChanged = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool rebuildPrefabs
|
|
{
|
|
set
|
|
{
|
|
if (value == true)
|
|
{
|
|
foreach (var camera in cameras.Values)
|
|
{
|
|
camera.rebuildPrefabs = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Destroy()
|
|
{
|
|
if (isDisposed)
|
|
return;
|
|
|
|
isDisposed = true;
|
|
|
|
_keysToRemove.Clear();
|
|
|
|
foreach (KeyValuePair<int, CameraRenderer> camera in cameras)
|
|
{
|
|
_keysToRemove.Add(camera.Key);
|
|
}
|
|
|
|
foreach (int id in _keysToRemove)
|
|
RemoveCamera(id);
|
|
|
|
maxInstanceCount = 0;
|
|
instanceUploadCount = 0;
|
|
|
|
uploadInstancesArrayQueue.Clear();
|
|
|
|
globalInstanceBuffer?.Dispose();
|
|
globalInstanceBuffer = null;
|
|
globalUpdateInstanceBuffer?.Dispose();
|
|
globalUpdateInstanceBuffer = null;
|
|
tempGlobalInstanceBuffer?.Dispose();
|
|
tempGlobalInstanceBuffer = null;
|
|
commandBuffer?.Dispose();
|
|
commandBuffer = null;
|
|
|
|
prefabDataFreeSlot.Clear();
|
|
instanceBufferFreeSlot.Clear();
|
|
_keysToRemove.Clear();
|
|
InstanceStreamer.nextStreamerId = 1;
|
|
CameraRenderer.nextCameraRendererId = 1;
|
|
|
|
uploadInstancesArray.Dispose();
|
|
}
|
|
|
|
internal static void BuildBuffers()
|
|
{
|
|
Monitor.Enter(threadSecurity);
|
|
bool deleteOldBuffer = false;
|
|
//Load shader TODO: (Move this to init?)
|
|
if(instancingShader == null)
|
|
{
|
|
uploadInstancesArray = new NativeArray<InstanceData>(STREAMOUT_SIZE, Allocator.Persistent);
|
|
|
|
instancingShader = UnityEngine.Object.Instantiate(Resources.Load<ComputeShader>("ThoMagic Renderer Instancing"));
|
|
instancingShader.hideFlags = HideFlags.HideAndDontSave;
|
|
uploadInstancesShaderId = instancingShader.FindKernel("Upload_64");
|
|
resizeInstanceBufferShaderId = instancingShader.FindKernel("Resize_64");
|
|
|
|
instancingShader.name = $"ThoMagic Renderer Instancing - RendererPool";
|
|
commandBuffer = new CommandBuffer();
|
|
}
|
|
|
|
commandBuffer.Clear();
|
|
|
|
//Are there queued instances ready for upload no current upload is planned?
|
|
if (instanceUploadCount == 0 && uploadInstancesArrayQueue.Count > 0)
|
|
{
|
|
int take = Math.Min(STREAMOUT_SIZE, uploadInstancesArrayQueue.Count);
|
|
for (int i = 0; i < take; i++)
|
|
{
|
|
uploadInstancesArray[instanceUploadCount++] = uploadInstancesArrayQueue.Dequeue();
|
|
}
|
|
|
|
instanceCountChanged = true;
|
|
}
|
|
|
|
//Upload instances
|
|
if (_instanceCountChanged && maxInstanceCount > 0)
|
|
{
|
|
//Create update buffer if null. TODO: Move to init?
|
|
if(globalUpdateInstanceBuffer == null)
|
|
{
|
|
globalUpdateInstanceBuffer = new ComputeBuffer(STREAMOUT_SIZE, InstanceData.Stride, ComputeBufferType.Structured);//, ComputeBufferMode.SubUpdates);
|
|
}
|
|
|
|
//Resize and copy global instance buffer in gpu
|
|
if (globalInstanceBuffer == null || globalInstanceBuffer.count < maxInstanceCount)
|
|
{
|
|
int nextCount = RESIZE_COUNT * ((maxInstanceCount - 1) / RESIZE_COUNT + 1);
|
|
|
|
tempGlobalInstanceBuffer = new ComputeBuffer(nextCount, InstanceData.Stride, ComputeBufferType.Structured);
|
|
|
|
tempGlobalInstanceBuffer.name = $"ThoMagic global instance buffer";
|
|
Debug.Log($"Upsized globalInstanceBuffer {nextCount.ToString("N")} / {(nextCount * InstanceData.Stride / 1024 / 1024)}mb");
|
|
|
|
if (globalInstanceBuffer != null)
|
|
{
|
|
deleteOldBuffer = true;
|
|
commandBuffer.SetComputeBufferParam(instancingShader, resizeInstanceBufferShaderId, "tempGlobalInstances", tempGlobalInstanceBuffer);
|
|
commandBuffer.SetComputeBufferParam(instancingShader, resizeInstanceBufferShaderId, "globalInstances", globalInstanceBuffer);
|
|
commandBuffer.SetComputeIntParam(instancingShader, "_CountResize", globalInstanceBuffer.count);
|
|
commandBuffer.DispatchCompute(instancingShader, resizeInstanceBufferShaderId, Mathf.CeilToInt(globalInstanceBuffer.count / 64.0f), 1, 1);
|
|
|
|
/*instancingShader.SetBuffer(resizeInstanceBufferShaderId, "tempGlobalInstances", tempGlobalInstanceBuffer);
|
|
instancingShader.SetBuffer(resizeInstanceBufferShaderId, "globalInstances", globalInstanceBuffer);
|
|
instancingShader.SetInt("_Count", globalInstanceBuffer.count);
|
|
instancingShader.Dispatch(resizeInstanceBufferShaderId, Mathf.CeilToInt(globalInstanceBuffer.count / 64.0f), 1, 1);
|
|
|
|
globalInstanceBuffer.Dispose();
|
|
globalInstanceBuffer = tempGlobalInstanceBuffer;
|
|
tempGlobalInstanceBuffer = null;*/
|
|
}
|
|
else
|
|
{
|
|
globalInstanceBuffer = tempGlobalInstanceBuffer;
|
|
//tempGlobalInstanceBuffer = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tempGlobalInstanceBuffer = globalInstanceBuffer;
|
|
}
|
|
|
|
//Upload
|
|
globalUpdateInstanceBuffer.SetData(uploadInstancesArray);
|
|
/*var updateTarget = globalUpdateInstanceBuffer.BeginWrite<InstanceData>(0, instanceUploadCount);
|
|
NativeArray<InstanceData>.Copy(uploadInstancesArray, updateTarget, instanceUploadCount);
|
|
globalUpdateInstanceBuffer.EndWrite<InstanceData>(instanceUploadCount);*/
|
|
|
|
/*instancingShader.SetBuffer(uploadInstancesShaderId, "globalUploadInstances", globalUpdateInstanceBuffer);
|
|
instancingShader.SetBuffer(uploadInstancesShaderId, "globalInstances", globalInstanceBuffer);
|
|
instancingShader.SetInt("_Count", instanceUploadCount);
|
|
instancingShader.Dispatch(uploadInstancesShaderId, Mathf.CeilToInt(instanceUploadCount / 64.0f), 1, 1);*/
|
|
|
|
commandBuffer.SetComputeBufferParam(instancingShader, uploadInstancesShaderId, "globalUploadInstances", globalUpdateInstanceBuffer);
|
|
commandBuffer.SetComputeBufferParam(instancingShader, uploadInstancesShaderId, "globalInstances", tempGlobalInstanceBuffer);
|
|
commandBuffer.SetComputeIntParam(instancingShader, "_CountUpload", instanceUploadCount);
|
|
commandBuffer.DispatchCompute(instancingShader, uploadInstancesShaderId, Mathf.CeilToInt(instanceUploadCount / 64.0f), 1, 1);
|
|
|
|
Graphics.ExecuteCommandBuffer(commandBuffer);
|
|
|
|
var fence = Graphics.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.ComputeProcessing);
|
|
Graphics.WaitOnAsyncGraphicsFence(fence);
|
|
|
|
if(deleteOldBuffer)
|
|
{
|
|
globalInstanceBuffer.Dispose();
|
|
globalInstanceBuffer = tempGlobalInstanceBuffer;
|
|
tempGlobalInstanceBuffer = null;
|
|
}
|
|
|
|
instanceUploadCount = 0;
|
|
_instanceCountChanged = false;
|
|
}
|
|
|
|
Monitor.Exit(threadSecurity);
|
|
}
|
|
|
|
#region Instance handling
|
|
internal static int AddInstance(int objectId, Vector3 pos, Quaternion orientation, float scaleXZ, float scaleY)
|
|
{
|
|
if (isDisposed)
|
|
return -1;
|
|
|
|
Monitor.Enter(threadSecurity);
|
|
|
|
var myId = maxInstanceCount;
|
|
var objectData = GetObjectData(objectId);
|
|
objectData.count++;
|
|
|
|
//Take free slot in instance buffer
|
|
if (instanceBufferFreeSlot.Count > 0)
|
|
{
|
|
myId = instanceBufferFreeSlot.Dequeue();
|
|
}
|
|
else
|
|
{
|
|
maxInstanceCount++;
|
|
}
|
|
|
|
//Directly upload if STREAMOUT_SIZE not reached, otherwise queue for upload
|
|
if (instanceUploadCount < STREAMOUT_SIZE && uploadInstancesArrayQueue.Count == 0)
|
|
uploadInstancesArray[instanceUploadCount++] = new InstanceData(InstanceData.Add, myId, objectData.prefabId, pos.x, pos.y, pos.z, orientation, scaleXZ, scaleY);
|
|
else
|
|
uploadInstancesArrayQueue.Enqueue(new InstanceData(InstanceData.Add, myId, objectData.prefabId, pos.x, pos.y, pos.z, orientation, scaleXZ, scaleY));
|
|
|
|
instanceCountChanged = true;
|
|
|
|
Monitor.Exit(threadSecurity);
|
|
|
|
return myId;
|
|
}
|
|
|
|
internal static void RemoveInstance(int objectId, int instanceId)
|
|
{
|
|
if (isDisposed)
|
|
return;
|
|
|
|
Monitor.Enter(threadSecurity);
|
|
|
|
var objectData = GetObjectData(objectId);
|
|
objectData.count--;
|
|
|
|
//No need to reset instance data because the cullable index is removed, instance data will be reset on reuse.
|
|
/*if (instanceUploadCount < STREAMOUT_SIZE && uploadInstancesArrayQueue.Count == 0)
|
|
uploadInstancesArray[instanceUploadCount++] = new InstanceData(InstanceData.Delete, instanceId);
|
|
else
|
|
uploadInstancesArrayQueue.Enqueue(new InstanceData(InstanceData.Delete, instanceId));*/
|
|
|
|
externInstanceCountChanged = true;
|
|
//Remember free slot in instance buffer
|
|
instanceBufferFreeSlot.Enqueue(instanceId);
|
|
|
|
Monitor.Exit(threadSecurity);
|
|
}
|
|
#endregion
|
|
|
|
#region Camera handling
|
|
public static IEnumerable<CameraRenderer> GetCameras()
|
|
{
|
|
return cameras.Values;
|
|
}
|
|
|
|
public static int RegisterCamera(Camera camera)
|
|
{
|
|
int num = camera != null ? camera.GetHashCode() : throw new ArgumentNullException(nameof(camera));
|
|
if (!cameras.ContainsKey(num))
|
|
{
|
|
CameraRenderer cameraRenderer = new CameraRenderer(camera);
|
|
cameraRenderer.SetFloatingOrigin(in _origin);
|
|
cameras.Add(num, cameraRenderer);
|
|
}
|
|
return num;
|
|
}
|
|
|
|
public static CameraRenderer GetCamera(int cameraId)
|
|
{
|
|
CameraRenderer cameraRenderer;
|
|
return cameras.TryGetValue(cameraId, out cameraRenderer) ? cameraRenderer : null;
|
|
}
|
|
|
|
public static bool RemoveCamera(int cameraId)
|
|
{
|
|
CameraRenderer cameraRenderer;
|
|
if (!cameras.TryGetValue(cameraId, out cameraRenderer))
|
|
return false;
|
|
cameraRenderer?.Dispose();
|
|
return cameras.Remove(cameraId);
|
|
}
|
|
|
|
public static void RemoveDestroyedCameras()
|
|
{
|
|
_keysToRemove.Clear();
|
|
|
|
foreach (KeyValuePair<int, CameraRenderer> camera in cameras)
|
|
{
|
|
if (camera.Value.Camera == null)
|
|
_keysToRemove.Add(camera.Key);
|
|
}
|
|
|
|
foreach (int id in _keysToRemove)
|
|
RemoveCamera(id);
|
|
}
|
|
#endregion
|
|
|
|
#region Object handling
|
|
public static int RegisterObject(GameObject gameObject, IInstanceRenderSettings settings, InstanceStreamer source, int ownerHash)
|
|
{
|
|
int num = gameObject != null ? /*gameObject.GetHashCode()*/ CalculateContentHash(gameObject) : throw new ArgumentNullException(nameof(gameObject));
|
|
|
|
if(!streamers.ContainsKey(ownerHash))
|
|
streamers.Add(ownerHash, source);
|
|
|
|
if (!objects.ContainsKey(num))
|
|
{
|
|
var obj = new ObjectData(gameObject, settings);
|
|
objects.Add(num, obj);
|
|
objectsList.Add(obj);
|
|
obj.contentHashCache = CalculateContentHash(num, false);
|
|
}
|
|
else
|
|
SetObjectSettings(gameObject, settings);
|
|
|
|
if (!objects[num].Owners.Contains(ownerHash))
|
|
objects[num].Owners.Add(ownerHash);
|
|
|
|
streamers[ownerHash].owners.Add(num);
|
|
|
|
return num;
|
|
}
|
|
|
|
public static void RemoveObject(int objectId, int ownerHash)
|
|
{
|
|
if (objects.TryGetValue(objectId, out var objectData))
|
|
{
|
|
objectData.Owners.Remove(ownerHash);
|
|
if (objectData.Owners.Count == 0)
|
|
{
|
|
objects.Remove(objectId);
|
|
objectsList.Remove(objectData);
|
|
prefabDataFreeSlot.Enqueue(objectData.prefabId);
|
|
}
|
|
}
|
|
|
|
if (streamers.TryGetValue(ownerHash, out var streamer))
|
|
{
|
|
streamer.owners.Remove(objectId);
|
|
|
|
if (streamer.owners.Count == 0)
|
|
streamers.Remove(ownerHash);
|
|
}
|
|
|
|
rebuildPrefabs = true;
|
|
}
|
|
|
|
public static GameObject GetObject(int objectId)
|
|
{
|
|
return objects.TryGetValue(objectId, out var objectData) ? objectData.GameObject : null;
|
|
}
|
|
|
|
public static ObjectData GetObjectData(int objectId)
|
|
{
|
|
return objects[objectId];
|
|
}
|
|
|
|
public static void SetObjectSettings(GameObject prefab, IInstanceRenderSettings instanceRenderSettings)
|
|
{
|
|
int hashCode = prefab.GetHashCode();
|
|
ObjectData objectData;
|
|
if (!objects.TryGetValue(hashCode, out objectData))
|
|
return;
|
|
|
|
if ((objectData.Settings == null && instanceRenderSettings != null)
|
|
|| (objectData.Settings != null && instanceRenderSettings == null)
|
|
|| instanceRenderSettings.Settings.GetHashCode() != objectData.Settings.Settings.GetHashCode())
|
|
{
|
|
objectData.Settings = instanceRenderSettings;
|
|
rebuildPrefabs = true;
|
|
}
|
|
}
|
|
|
|
private static int CalculateContentHash(GameObject gameObject)
|
|
{
|
|
int num = 13;
|
|
if (gameObject == null)
|
|
return num;
|
|
|
|
var lodGroup = gameObject.GetComponent<LODGroup>();
|
|
|
|
LOD[] lodArray;
|
|
|
|
if (lodGroup == null)
|
|
lodArray = new LOD[1]
|
|
{
|
|
new LOD(0.0001f, gameObject.GetComponentsInChildren<MeshRenderer>())
|
|
};
|
|
else
|
|
lodArray = lodGroup.GetLODs();
|
|
|
|
|
|
foreach (LOD loD in lodArray)
|
|
num = HashCode.Combine<int, int>(num, CalculateContentHash(loD));
|
|
|
|
return num;
|
|
}
|
|
|
|
public static int CalculateContentHash(int objectId, bool reload = false)
|
|
{
|
|
var obj = objects[objectId];
|
|
|
|
int num = 13;
|
|
if (obj.GameObject == null)
|
|
return num;
|
|
|
|
if (reload)
|
|
{
|
|
var lodGroup = obj.GameObject.GetComponent<LODGroup>();
|
|
|
|
LOD[] lodArray;
|
|
|
|
if (lodGroup == null)
|
|
lodArray = new LOD[1]
|
|
{
|
|
new LOD(0.0001f, obj.GameObject.GetComponentsInChildren<MeshRenderer>())
|
|
};
|
|
else
|
|
lodArray = lodGroup.GetLODs();
|
|
|
|
obj.LODs = lodArray;
|
|
}
|
|
|
|
foreach (LOD loD in obj.LODs)
|
|
num = HashCode.Combine<int, int>(num, CalculateContentHash(loD));
|
|
|
|
return num;
|
|
}
|
|
|
|
private static int CalculateContentHash(LOD lod)
|
|
{
|
|
int num = 13;
|
|
if (lod.renderers != null)
|
|
{
|
|
foreach (var renderer in lod.renderers)
|
|
{
|
|
if (renderer == null)
|
|
{
|
|
num = HashCode.Combine<int, int>(num, 13);
|
|
}
|
|
else
|
|
{
|
|
MeshFilter component = renderer.GetComponent<MeshFilter>();
|
|
|
|
num = HashCode.Combine<int, int, int, int>(HashCode.Combine<int, int, int, int, int, int, int, int>(
|
|
num,
|
|
component == null || component.sharedMesh == null ? 13 : component.sharedMesh.GetHashCode(),
|
|
renderer.shadowCastingMode.GetHashCode(),
|
|
CalculateContentHash(renderer.sharedMaterials),
|
|
renderer.motionVectorGenerationMode.GetHashCode(),
|
|
renderer.receiveShadows.GetHashCode(),
|
|
renderer.rendererPriority.GetHashCode(),
|
|
renderer.renderingLayerMask.GetHashCode()),
|
|
renderer.gameObject.layer.GetHashCode(),
|
|
renderer.gameObject.transform.localPosition.GetHashCode(),
|
|
lod.screenRelativeTransitionHeight.GetHashCode());
|
|
}
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
private static int CalculateContentHash(Material[] materials)
|
|
{
|
|
int num = 13;
|
|
if (materials != null)
|
|
{
|
|
foreach (Material material in materials)
|
|
num = HashCode.Combine<int, int>(num,
|
|
material != null ? material.GetHashCode() : 13);
|
|
}
|
|
return num;
|
|
}
|
|
|
|
public static bool ContentHashChanged(int objectId)
|
|
{
|
|
if (!objects.ContainsKey(objectId))
|
|
return false;
|
|
|
|
return objects[objectId].contentHashCache != CalculateContentHash(objectId, true);
|
|
}
|
|
|
|
internal static void Initialize()
|
|
{
|
|
isDisposed = false;
|
|
BuildBuffers();
|
|
}
|
|
#endregion
|
|
}
|
|
}
|