You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
520 lines
20 KiB
520 lines
20 KiB
using System; |
|
using System.Collections.Generic; |
|
using Unity.Collections; |
|
using Unity.Mathematics; |
|
using UnityEngine.Assertions; |
|
using UnityEngine.Jobs; |
|
|
|
namespace UnityEngine.Rendering.Universal |
|
{ |
|
internal class DecalEntityIndexer |
|
{ |
|
public struct DecalEntityItem |
|
{ |
|
public int chunkIndex; |
|
public int arrayIndex; |
|
public int version; |
|
} |
|
|
|
private List<DecalEntityItem> m_Entities = new List<DecalEntityItem>(); |
|
private Queue<int> m_FreeIndices = new Queue<int>(); |
|
|
|
public bool IsValid(DecalEntity decalEntity) |
|
{ |
|
if (m_Entities.Count <= decalEntity.index) |
|
return false; |
|
|
|
return m_Entities[decalEntity.index].version == decalEntity.version; |
|
} |
|
|
|
public DecalEntity CreateDecalEntity(int arrayIndex, int chunkIndex) |
|
{ |
|
// Reuse |
|
if (m_FreeIndices.Count != 0) |
|
{ |
|
int entityIndex = m_FreeIndices.Dequeue(); |
|
int newVersion = m_Entities[entityIndex].version + 1; |
|
|
|
m_Entities[entityIndex] = new DecalEntityItem() |
|
{ |
|
arrayIndex = arrayIndex, |
|
chunkIndex = chunkIndex, |
|
version = newVersion, |
|
}; |
|
|
|
return new DecalEntity() |
|
{ |
|
index = entityIndex, |
|
version = newVersion, |
|
}; |
|
} |
|
|
|
// Create new one |
|
{ |
|
int entityIndex = m_Entities.Count; |
|
int version = 1; |
|
|
|
m_Entities.Add(new DecalEntityItem() |
|
{ |
|
arrayIndex = arrayIndex, |
|
chunkIndex = chunkIndex, |
|
version = version, |
|
}); |
|
|
|
|
|
return new DecalEntity() |
|
{ |
|
index = entityIndex, |
|
version = version, |
|
}; |
|
} |
|
} |
|
|
|
public void DestroyDecalEntity(DecalEntity decalEntity) |
|
{ |
|
Assert.IsTrue(IsValid(decalEntity)); |
|
m_FreeIndices.Enqueue(decalEntity.index); |
|
|
|
// Update version that everything that points to it will have outdated version |
|
var item = m_Entities[decalEntity.index]; |
|
item.version++; |
|
m_Entities[decalEntity.index] = item; |
|
} |
|
|
|
public DecalEntityItem GetItem(DecalEntity decalEntity) |
|
{ |
|
Assert.IsTrue(IsValid(decalEntity)); |
|
return m_Entities[decalEntity.index]; |
|
} |
|
|
|
public void UpdateIndex(DecalEntity decalEntity, int newArrayIndex) |
|
{ |
|
Assert.IsTrue(IsValid(decalEntity)); |
|
var item = m_Entities[decalEntity.index]; |
|
item.arrayIndex = newArrayIndex; |
|
item.version = decalEntity.version; |
|
m_Entities[decalEntity.index] = item; |
|
} |
|
|
|
public void RemapChunkIndices(List<int> remaper) |
|
{ |
|
for (int i = 0; i < m_Entities.Count; ++i) |
|
{ |
|
int newChunkIndex = remaper[m_Entities[i].chunkIndex]; |
|
var item = m_Entities[i]; |
|
item.chunkIndex = newChunkIndex; |
|
m_Entities[i] = item; |
|
} |
|
} |
|
|
|
public void Clear() |
|
{ |
|
m_Entities.Clear(); |
|
m_FreeIndices.Clear(); |
|
} |
|
} |
|
|
|
internal struct DecalEntity |
|
{ |
|
public int index; |
|
public int version; |
|
} |
|
|
|
/// <summary> |
|
/// Contains <see cref="DecalEntity"/> and shared material. |
|
/// </summary> |
|
internal class DecalEntityChunk : DecalChunk |
|
{ |
|
public Material material; |
|
public NativeArray<DecalEntity> decalEntities; |
|
public DecalProjector[] decalProjectors; |
|
public TransformAccessArray transformAccessArray; |
|
|
|
public override void Push() |
|
{ |
|
count++; |
|
} |
|
|
|
public override void RemoveAtSwapBack(int entityIndex) |
|
{ |
|
RemoveAtSwapBack(ref decalEntities, entityIndex, count); |
|
RemoveAtSwapBack(ref decalProjectors, entityIndex, count); |
|
transformAccessArray.RemoveAtSwapBack(entityIndex); |
|
count--; |
|
} |
|
|
|
public override void SetCapacity(int newCapacity) |
|
{ |
|
decalEntities.ResizeArray(newCapacity); |
|
ResizeNativeArray(ref transformAccessArray, decalProjectors, newCapacity); |
|
ArrayExtensions.ResizeArray(ref decalProjectors, newCapacity); |
|
capacity = newCapacity; |
|
} |
|
|
|
public override void Dispose() |
|
{ |
|
if (capacity == 0) |
|
return; |
|
|
|
decalEntities.Dispose(); |
|
transformAccessArray.Dispose(); |
|
decalProjectors = null; |
|
count = 0; |
|
capacity = 0; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Manages lifetime between <see cref="DecalProjector"></see> and <see cref="DecalEntity"/>. |
|
/// Contains all <see cref="DecalChunk"/>. |
|
/// </summary> |
|
internal class DecalEntityManager : IDisposable |
|
{ |
|
public List<DecalEntityChunk> entityChunks = new List<DecalEntityChunk>(); |
|
public List<DecalCachedChunk> cachedChunks = new List<DecalCachedChunk>(); |
|
public List<DecalCulledChunk> culledChunks = new List<DecalCulledChunk>(); |
|
public List<DecalDrawCallChunk> drawCallChunks = new List<DecalDrawCallChunk>(); |
|
public int chunkCount; |
|
|
|
private ProfilingSampler m_AddDecalSampler; |
|
private ProfilingSampler m_ResizeChunks; |
|
private ProfilingSampler m_SortChunks; |
|
|
|
private DecalEntityIndexer m_DecalEntityIndexer = new DecalEntityIndexer(); |
|
private Dictionary<Material, int> m_MaterialToChunkIndex = new Dictionary<Material, int>(); |
|
|
|
private struct CombinedChunks |
|
{ |
|
public DecalEntityChunk entityChunk; |
|
public DecalCachedChunk cachedChunk; |
|
public DecalCulledChunk culledChunk; |
|
public DecalDrawCallChunk drawCallChunk; |
|
public int previousChunkIndex; |
|
public bool valid; |
|
} |
|
private List<CombinedChunks> m_CombinedChunks = new List<CombinedChunks>(); |
|
private List<int> m_CombinedChunkRemmap = new List<int>(); |
|
|
|
private Material m_ErrorMaterial; |
|
public Material errorMaterial |
|
{ |
|
get |
|
{ |
|
if (m_ErrorMaterial == null) |
|
m_ErrorMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/InternalErrorShader")); |
|
return m_ErrorMaterial; |
|
} |
|
} |
|
|
|
private Mesh m_DecalProjectorMesh; |
|
public Mesh decalProjectorMesh |
|
{ |
|
get |
|
{ |
|
if (m_DecalProjectorMesh == null) |
|
m_DecalProjectorMesh = CoreUtils.CreateCubeMesh(new Vector4(-0.5f, -0.5f, -0.5f, 1.0f), new Vector4(0.5f, 0.5f, 0.5f, 1.0f)); |
|
return m_DecalProjectorMesh; |
|
} |
|
} |
|
|
|
public DecalEntityManager() |
|
{ |
|
m_AddDecalSampler = new ProfilingSampler("DecalEntityManager.CreateDecalEntity"); |
|
m_ResizeChunks = new ProfilingSampler("DecalEntityManager.ResizeChunks"); |
|
m_SortChunks = new ProfilingSampler("DecalEntityManager.SortChunks"); |
|
} |
|
|
|
public bool IsValid(DecalEntity decalEntity) |
|
{ |
|
return m_DecalEntityIndexer.IsValid(decalEntity); |
|
} |
|
|
|
public DecalEntity CreateDecalEntity(DecalProjector decalProjector) |
|
{ |
|
var material = decalProjector.material; |
|
if (material == null) |
|
material = errorMaterial; |
|
|
|
using (new ProfilingScope(null, m_AddDecalSampler)) |
|
{ |
|
int chunkIndex = CreateChunkIndex(material); |
|
int entityIndex = entityChunks[chunkIndex].count; |
|
|
|
DecalEntity entity = m_DecalEntityIndexer.CreateDecalEntity(entityIndex, chunkIndex); |
|
|
|
DecalEntityChunk entityChunk = entityChunks[chunkIndex]; |
|
DecalCachedChunk cachedChunk = cachedChunks[chunkIndex]; |
|
DecalCulledChunk culledChunk = culledChunks[chunkIndex]; |
|
DecalDrawCallChunk drawCallChunk = drawCallChunks[chunkIndex]; |
|
|
|
// Make sure we have space to add new entity |
|
if (entityChunks[chunkIndex].capacity == entityChunks[chunkIndex].count) |
|
{ |
|
using (new ProfilingScope(null, m_ResizeChunks)) |
|
{ |
|
int newCapacity = entityChunks[chunkIndex].capacity + entityChunks[chunkIndex].capacity; |
|
newCapacity = math.max(8, newCapacity); |
|
|
|
entityChunk.SetCapacity(newCapacity); |
|
cachedChunk.SetCapacity(newCapacity); |
|
culledChunk.SetCapacity(newCapacity); |
|
drawCallChunk.SetCapacity(newCapacity); |
|
} |
|
} |
|
|
|
entityChunk.Push(); |
|
cachedChunk.Push(); |
|
culledChunk.Push(); |
|
drawCallChunk.Push(); |
|
|
|
entityChunk.decalProjectors[entityIndex] = decalProjector; |
|
entityChunk.decalEntities[entityIndex] = entity; |
|
entityChunk.transformAccessArray.Add(decalProjector.transform); |
|
|
|
UpdateDecalEntityData(entity, decalProjector); |
|
|
|
return entity; |
|
} |
|
} |
|
|
|
private int CreateChunkIndex(Material material) |
|
{ |
|
if (!m_MaterialToChunkIndex.TryGetValue(material, out int chunkIndex)) |
|
{ |
|
var propertyBlock = new MaterialPropertyBlock(); |
|
|
|
// In order instanced and non instanced rendering to work with _NormalToWorld |
|
// We need to make sure array is created with maximum size |
|
propertyBlock.SetMatrixArray("_NormalToWorld", new Matrix4x4[250]); |
|
|
|
entityChunks.Add(new DecalEntityChunk() { material = material }); |
|
cachedChunks.Add(new DecalCachedChunk() |
|
{ |
|
propertyBlock = propertyBlock, |
|
}); |
|
|
|
culledChunks.Add(new DecalCulledChunk()); |
|
drawCallChunks.Add(new DecalDrawCallChunk() { subCallCounts = new NativeArray<int>(1, Allocator.Persistent) }); |
|
|
|
m_CombinedChunks.Add(new CombinedChunks()); |
|
m_CombinedChunkRemmap.Add(0); |
|
|
|
m_MaterialToChunkIndex.Add(material, chunkCount); |
|
return chunkCount++; |
|
} |
|
|
|
return chunkIndex; |
|
} |
|
|
|
public void UpdateDecalEntityData(DecalEntity decalEntity, DecalProjector decalProjector) |
|
{ |
|
var decalItem = m_DecalEntityIndexer.GetItem(decalEntity); |
|
|
|
int chunkIndex = decalItem.chunkIndex; |
|
int arrayIndex = decalItem.arrayIndex; |
|
|
|
DecalCachedChunk cachedChunk = cachedChunks[chunkIndex]; |
|
|
|
cachedChunk.sizeOffsets[arrayIndex] = Matrix4x4.Translate(decalProjector.decalOffset) * Matrix4x4.Scale(decalProjector.decalSize); |
|
|
|
float drawDistance = decalProjector.drawDistance; |
|
float fadeScale = decalProjector.fadeScale; |
|
float startAngleFade = decalProjector.startAngleFade; |
|
float endAngleFade = decalProjector.endAngleFade; |
|
Vector4 uvScaleBias = decalProjector.uvScaleBias; |
|
int layerMask = decalProjector.gameObject.layer; |
|
ulong sceneLayerMask = decalProjector.gameObject.sceneCullingMask; |
|
float fadeFactor = decalProjector.fadeFactor; |
|
|
|
cachedChunk.drawDistances[arrayIndex] = new Vector2(drawDistance, fadeScale); |
|
// In the shader to remap from cosine -1 to 1 to new range 0..1 (with 0 - 0 degree and 1 - 180 degree) |
|
// we do 1.0 - (dot() * 0.5 + 0.5) => 0.5 * (1 - dot()) |
|
// we actually square that to get smoother result => x = (0.5 - 0.5 * dot())^2 |
|
// Do a remap in the shader. 1.0 - saturate((x - start) / (end - start)) |
|
// After simplification => saturate(a + b * dot() * (dot() - 2.0)) |
|
// a = 1.0 - (0.25 - start) / (end - start), y = - 0.25 / (end - start) |
|
if (startAngleFade == 180.0f) // angle fade is disabled |
|
{ |
|
cachedChunk.angleFades[arrayIndex] = new Vector2(0.0f, 0.0f); |
|
} |
|
else |
|
{ |
|
float angleStart = startAngleFade / 180.0f; |
|
float angleEnd = endAngleFade / 180.0f; |
|
var range = Mathf.Max(0.0001f, angleEnd - angleStart); |
|
cachedChunk.angleFades[arrayIndex] = new Vector2(1.0f - (0.25f - angleStart) / range, -0.25f / range); |
|
} |
|
cachedChunk.uvScaleBias[arrayIndex] = uvScaleBias; |
|
cachedChunk.layerMasks[arrayIndex] = layerMask; |
|
cachedChunk.sceneLayerMasks[arrayIndex] = sceneLayerMask; |
|
cachedChunk.fadeFactors[arrayIndex] = fadeFactor; |
|
cachedChunk.scaleModes[arrayIndex] = decalProjector.scaleMode; |
|
|
|
cachedChunk.positions[arrayIndex] = decalProjector.transform.position; |
|
cachedChunk.rotation[arrayIndex] = decalProjector.transform.rotation; |
|
cachedChunk.scales[arrayIndex] = decalProjector.transform.lossyScale; |
|
cachedChunk.dirty[arrayIndex] = true; |
|
} |
|
|
|
public void DestroyDecalEntity(DecalEntity decalEntity) |
|
{ |
|
if (!m_DecalEntityIndexer.IsValid(decalEntity)) |
|
return; |
|
|
|
var decalItem = m_DecalEntityIndexer.GetItem(decalEntity); |
|
m_DecalEntityIndexer.DestroyDecalEntity(decalEntity); |
|
|
|
int chunkIndex = decalItem.chunkIndex; |
|
int arrayIndex = decalItem.arrayIndex; |
|
|
|
DecalEntityChunk entityChunk = entityChunks[chunkIndex]; |
|
DecalCachedChunk cachedChunk = cachedChunks[chunkIndex]; |
|
DecalCulledChunk culledChunk = culledChunks[chunkIndex]; |
|
DecalDrawCallChunk drawCallChunk = drawCallChunks[chunkIndex]; |
|
|
|
int lastArrayIndex = entityChunk.count - 1; |
|
if (arrayIndex != lastArrayIndex) |
|
m_DecalEntityIndexer.UpdateIndex(entityChunk.decalEntities[lastArrayIndex], arrayIndex); |
|
|
|
entityChunk.RemoveAtSwapBack(arrayIndex); |
|
cachedChunk.RemoveAtSwapBack(arrayIndex); |
|
culledChunk.RemoveAtSwapBack(arrayIndex); |
|
drawCallChunk.RemoveAtSwapBack(arrayIndex); |
|
} |
|
|
|
public void Update() |
|
{ |
|
using (new ProfilingScope(null, m_SortChunks)) |
|
{ |
|
for (int i = 0; i < chunkCount; ++i) |
|
{ |
|
if (entityChunks[i].material == null) |
|
entityChunks[i].material = errorMaterial; |
|
} |
|
|
|
// Combine chunks into single array |
|
for (int i = 0; i < chunkCount; ++i) |
|
{ |
|
m_CombinedChunks[i] = new CombinedChunks() |
|
{ |
|
entityChunk = entityChunks[i], |
|
cachedChunk = cachedChunks[i], |
|
culledChunk = culledChunks[i], |
|
drawCallChunk = drawCallChunks[i], |
|
previousChunkIndex = i, |
|
valid = entityChunks[i].count != 0, |
|
}; |
|
} |
|
|
|
|
|
// Sort |
|
m_CombinedChunks.Sort((a, b) => |
|
{ |
|
if (a.valid && !b.valid) |
|
return -1; |
|
if (!a.valid && b.valid) |
|
return 1; |
|
|
|
if (a.cachedChunk.drawOrder < b.cachedChunk.drawOrder) |
|
return -1; |
|
if (a.cachedChunk.drawOrder > b.cachedChunk.drawOrder) |
|
return 1; |
|
return a.entityChunk.material.GetHashCode().CompareTo(b.entityChunk.material.GetHashCode()); |
|
}); |
|
|
|
// Early out if nothing changed |
|
bool dirty = false; |
|
for (int i = 0; i < chunkCount; ++i) |
|
{ |
|
if (m_CombinedChunks[i].previousChunkIndex != i || !m_CombinedChunks[i].valid) |
|
{ |
|
dirty = true; |
|
break; |
|
} |
|
} |
|
if (!dirty) |
|
return; |
|
|
|
// Update chunks |
|
int count = 0; |
|
m_MaterialToChunkIndex.Clear(); |
|
for (int i = 0; i < chunkCount; ++i) |
|
{ |
|
var combinedChunk = m_CombinedChunks[i]; |
|
|
|
// Destroy invalid chunk |
|
if (!m_CombinedChunks[i].valid) |
|
{ |
|
combinedChunk.entityChunk.currentJobHandle.Complete(); |
|
combinedChunk.cachedChunk.currentJobHandle.Complete(); |
|
combinedChunk.culledChunk.currentJobHandle.Complete(); |
|
combinedChunk.drawCallChunk.currentJobHandle.Complete(); |
|
|
|
combinedChunk.entityChunk.Dispose(); |
|
combinedChunk.cachedChunk.Dispose(); |
|
combinedChunk.culledChunk.Dispose(); |
|
combinedChunk.drawCallChunk.Dispose(); |
|
|
|
continue; |
|
} |
|
|
|
entityChunks[i] = combinedChunk.entityChunk; |
|
cachedChunks[i] = combinedChunk.cachedChunk; |
|
culledChunks[i] = combinedChunk.culledChunk; |
|
drawCallChunks[i] = combinedChunk.drawCallChunk; |
|
if (!m_MaterialToChunkIndex.ContainsKey(entityChunks[i].material)) |
|
m_MaterialToChunkIndex.Add(entityChunks[i].material, i); |
|
m_CombinedChunkRemmap[combinedChunk.previousChunkIndex] = i; |
|
count++; |
|
} |
|
|
|
// In case some chunks where destroyed resize the arrays |
|
if (chunkCount > count) |
|
{ |
|
entityChunks.RemoveRange(count, chunkCount - count); |
|
cachedChunks.RemoveRange(count, chunkCount - count); |
|
culledChunks.RemoveRange(count, chunkCount - count); |
|
drawCallChunks.RemoveRange(count, chunkCount - count); |
|
m_CombinedChunks.RemoveRange(count, chunkCount - count); |
|
chunkCount = count; |
|
} |
|
|
|
// Remap entities chunk index with new sorted ones |
|
m_DecalEntityIndexer.RemapChunkIndices(m_CombinedChunkRemmap); |
|
} |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
CoreUtils.Destroy(m_ErrorMaterial); |
|
CoreUtils.Destroy(m_DecalProjectorMesh); |
|
|
|
foreach (var entityChunk in entityChunks) |
|
entityChunk.currentJobHandle.Complete(); |
|
foreach (var cachedChunk in cachedChunks) |
|
cachedChunk.currentJobHandle.Complete(); |
|
foreach (var culledChunk in culledChunks) |
|
culledChunk.currentJobHandle.Complete(); |
|
foreach (var drawCallChunk in drawCallChunks) |
|
drawCallChunk.currentJobHandle.Complete(); |
|
|
|
foreach (var entityChunk in entityChunks) |
|
entityChunk.Dispose(); |
|
foreach (var cachedChunk in cachedChunks) |
|
cachedChunk.Dispose(); |
|
foreach (var culledChunk in culledChunks) |
|
culledChunk.Dispose(); |
|
foreach (var drawCallChunk in drawCallChunks) |
|
drawCallChunk.Dispose(); |
|
|
|
m_DecalEntityIndexer.Clear(); |
|
m_MaterialToChunkIndex.Clear(); |
|
entityChunks.Clear(); |
|
cachedChunks.Clear(); |
|
culledChunks.Clear(); |
|
drawCallChunks.Clear(); |
|
m_CombinedChunks.Clear(); |
|
chunkCount = 0; |
|
} |
|
} |
|
}
|
|
|