ThomagicRenderer/Runtime/RendererPool.cs

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
}
}