#if SURFACE_CACHE || ENABLE_PATH_TRACING_SRP using System; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Rendering.LiveGI { internal class SceneChanges { public List addedInstances; public List changedInstances; public List removedInstances; public List addedMaterials; public List removedMaterials; public List changedMaterials; public List addedLights; public List changedLights; public List removedLights; public SceneChanges() { addedInstances = new List(); changedInstances = new List(); removedInstances = new List(); addedMaterials = new List(); removedMaterials = new List(); changedMaterials = new List(); addedLights = new List(); changedLights = new List(); removedLights = new List(); } public bool HasChanges() { return (addedInstances.Count | removedInstances.Count | changedInstances.Count | addedMaterials.Count | removedMaterials.Count | changedMaterials.Count | addedLights.Count | removedLights.Count | changedLights.Count) != 0; } public void Clear() { addedInstances.Clear(); removedInstances.Clear(); changedInstances.Clear(); addedMaterials.Clear(); removedMaterials.Clear(); changedMaterials.Clear(); addedLights.Clear(); removedLights.Clear(); changedLights.Clear(); } } [System.Flags] internal enum ModifiedProperties { Transform = 1, Material = 2, IsStatic = 4, ShadowCasting = 8, Layer = 16 } internal struct InstanceChanges { public MeshRenderer meshRenderer; public ModifiedProperties changes; } internal class SceneUpdatesTracker : IDisposable { struct Timestamp { public uint lastVisit; public uint creation; } class MaterialData { public MaterialData(Material material, uint timestamp) { this.timestamp.creation = timestamp; this.timestamp.lastVisit = timestamp; this.material = material; metaPassIndex = material.FindPass("Meta"); #if UNITY_EDITOR shaderCompiled = metaPassIndex != -1 ? ShaderUtil.IsPassCompiled(material, metaPassIndex) : true; #endif } public Material material; public int metaPassIndex; #if UNITY_EDITOR public bool shaderCompiled; #endif public Timestamp timestamp; } class InstanceData { public Timestamp timestamp; public EntityId[] materialIDs; public Material[] materials; public MeshRenderer renderer; public bool isStatic; public ShadowCastingMode shadowCastingMode; } class LightData { public Light light; public Timestamp timestamp; } ObjectDispatcher m_ObjectDispatcher; Dictionary m_Instances; Dictionary m_Materials; Dictionary m_Lights; SceneChanges m_Changes; uint m_Timestamp; public SceneUpdatesTracker() { m_Changes = new SceneChanges(); m_Instances = new Dictionary(); m_Materials = new Dictionary(); m_Lights = new Dictionary(); m_ObjectDispatcher = new ObjectDispatcher(); #if UNITY_EDITOR m_ObjectDispatcher.maxDispatchHistoryFramesCount = int.MaxValue; #endif m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects); m_ObjectDispatcher.EnableTransformTracking(ObjectDispatcher.TransformTrackingType.GlobalTRS); m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects | ObjectDispatcher.TypeTrackingFlags.Assets); m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects); m_ObjectDispatcher.EnableTransformTracking(ObjectDispatcher.TransformTrackingType.GlobalTRS); } public void Dispose() { m_ObjectDispatcher.Dispose(); m_Changes.Clear(); } public SceneChanges GetChanges(bool filterBakedLights) { m_Timestamp++; m_Changes.Clear(); FindInstancesChanges(); FindMaterialsChanges(); FindLightChanges(filterBakedLights); return m_Changes; } private void FindMaterialsChanges() { using var materialChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); // Handle added materials foreach (var instance in m_Instances.Values) { foreach (var material in instance.materials) { MaterialData data = null; if (material == null) continue; if (m_Materials.TryGetValue(material.GetEntityId(), out data)) { data.timestamp.lastVisit = m_Timestamp; } else { m_Changes.addedMaterials.Add(material); m_Materials.Add(material.GetEntityId(), new MaterialData(material, m_Timestamp)); } } } var justCompiledMaterials = new HashSet(); // Handle removed and uncompiled materials foreach (var materialKeyValue in m_Materials) { var materialID = materialKeyValue.Key; var matData = materialKeyValue.Value; if (matData.timestamp.lastVisit != m_Timestamp) m_Changes.removedMaterials.Add(materialID); #if UNITY_EDITOR else if (!matData.shaderCompiled) { if (ShaderUtil.IsPassCompiled(matData.material, matData.metaPassIndex)) { matData.shaderCompiled = true; justCompiledMaterials.Add(matData.material); } } #endif } foreach (var key in m_Changes.removedMaterials) m_Materials.Remove(key); // Handle changed materials foreach (Material changedMaterial in materialChanges.changed) { MaterialData data; if (!m_Materials.TryGetValue(changedMaterial.GetEntityId(), out data)) continue; if (data.timestamp.creation == m_Timestamp) // if this is an instance that has just been added continue; m_Changes.changedMaterials.Add(changedMaterial); } #if UNITY_EDITOR if (justCompiledMaterials.Count != 0) { foreach (var m in m_Changes.changedMaterials) justCompiledMaterials.Add(m); m_Changes.changedMaterials = new List(justCompiledMaterials); } #endif } private void FindInstancesChanges() { // Handle changed instances using var meshRendererChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); var transformChanges = m_ObjectDispatcher.GetTransformChangesAndClear(ObjectDispatcher.TransformTrackingType.GlobalTRS, false); var changedRenderers = MergeChanges(meshRendererChanges.changed, transformChanges); // Handle removed instances foreach (var key in meshRendererChanges.destroyedID) { m_Changes.removedInstances.Add(key); } // Update the remaining timestamps of the active mesh renderers List keys = new List(m_Instances.Keys); foreach (var key in keys) { if (!m_Instances[key].renderer) continue; if (m_Instances[key].renderer.enabled && m_Instances[key].renderer.gameObject.activeInHierarchy) { m_Instances[key].timestamp.lastVisit = m_Timestamp; } else { m_Changes.removedInstances.Add(key); } } foreach (var key in m_Changes.removedInstances) m_Instances.Remove(key); foreach (var item in changedRenderers) { var meshRenderer = item.Value.objectReference; bool tranformChanged = item.Value.transformChanged; if (!meshRenderer.enabled || !meshRenderer.gameObject.activeInHierarchy) { continue; } InstanceData oldData; if (!m_Instances.TryGetValue(meshRenderer.GetEntityId(), out oldData)) { // This renderer was just added var newData = CreateInstanceData(m_Timestamp, meshRenderer); m_Instances.Add(meshRenderer.GetEntityId(), newData); m_Changes.addedInstances.Add(meshRenderer); continue; } var data = CreateInstanceData(m_Timestamp, meshRenderer); ModifiedProperties changes = 0; if (tranformChanged) changes |= ModifiedProperties.Transform; bool IntArraySequenceEqual(EntityId[] firstArray, EntityId[] secondArray) => ((ReadOnlySpan)firstArray).SequenceEqual(secondArray); if (!IntArraySequenceEqual(oldData.materialIDs, data.materialIDs)) changes |= ModifiedProperties.Material; if (oldData.isStatic != data.isStatic) changes |= ModifiedProperties.IsStatic; if (oldData.shadowCastingMode != data.shadowCastingMode) changes |= ModifiedProperties.ShadowCasting; if (changes != 0) { m_Changes.changedInstances.Add(new InstanceChanges() { changes = changes, meshRenderer = meshRenderer }); m_Instances[meshRenderer.GetEntityId()] = data; } } } InstanceData CreateInstanceData(uint timestamp, MeshRenderer meshRenderer) { return new InstanceData() { timestamp = new Timestamp { lastVisit = timestamp, creation = timestamp }, isStatic = meshRenderer.gameObject.isStatic, materials = meshRenderer.sharedMaterials, materialIDs = Array.ConvertAll(meshRenderer.sharedMaterials, mat => mat != null ? mat.GetEntityId() : EntityId.None), shadowCastingMode = meshRenderer.shadowCastingMode, renderer = meshRenderer, }; } private void FindLightChanges(bool filterBakedLights) { // Handle changed lights using var lightChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); var lightTransformChanges = m_ObjectDispatcher.GetTransformChangesAndClear(ObjectDispatcher.TransformTrackingType.GlobalTRS, false); var changedLights = MergeChanges(lightChanges.changed, lightTransformChanges); // Handle removed lights foreach (var key in lightChanges.destroyedID) { if (m_Lights.ContainsKey(key)) m_Changes.removedLights.Add(key); } // Update the remaining timestamps of the active lights List keys = new List(m_Lights.Keys); foreach (var key in keys) { if (!m_Lights[key].light) continue; bool isBakedLight = m_Lights[key].light.bakingOutput.isBaked; if (m_Lights[key].light.enabled && m_Lights[key].light.gameObject.activeInHierarchy && !(filterBakedLights && isBakedLight)) { m_Lights[key].timestamp.lastVisit = m_Timestamp; } else { m_Changes.removedLights.Add(key); } } foreach (var key in m_Changes.removedLights) m_Lights.Remove(key); foreach (var item in changedLights) { var light = item.Value.objectReference; bool isBakedLight = light.bakingOutput.lightmapBakeType == LightmapBakeType.Baked; if (!light.enabled || !light.gameObject.activeInHierarchy || (filterBakedLights && isBakedLight)) { continue; } var newData = CreateLightData(m_Timestamp, light); // Newly added lights if (!m_Lights.ContainsKey(light.GetEntityId())) { m_Lights.Add(light.GetEntityId(), newData); m_Changes.addedLights.Add(light); continue; } // Changed lights m_Changes.changedLights.Add(light); m_Lights[light.GetEntityId()] = newData; } } LightData CreateLightData(uint timestamp, Light light) { return new LightData() { timestamp = new Timestamp { lastVisit = timestamp, creation = timestamp }, light = light, }; } struct ChangedObject where T : Component { public T objectReference; public bool transformChanged; } Dictionary> MergeChanges(Object[] changedRenderers, Component[] changedTransforms) where T : Component { var map = new Dictionary>(); foreach (Component component in changedTransforms) { map.TryAdd(component.GetEntityId(), new ChangedObject() { objectReference = (T)component, transformChanged = true }); } foreach (Component component in changedRenderers) { map.TryAdd(component.GetEntityId(), new ChangedObject() { objectReference = (T)component, transformChanged = false }); } return map; } } } #endif