using System; using System.Collections.Generic; namespace UnityEngine.Rendering.HighDefinition { // Multi-layered camera cache for reflection probes. // The goal is to keep a pool of camera GameObjects to avoid reallocating them regularly (especially when doing OnDemand updates) // On top of that, we keep a map of cameras currently used for a particular probe/face/position tuple. This allows us to keep frame to frame history coherency for real time probes. class ProbeCameraCache : IDisposable { // Pool of cameras Stack m_CameraPool = new Stack(); // Map of currently used cameras. Dictionary m_Cache = new Dictionary(); // Only used as temporary container. K[] m_TempCameraKeysCache = new K[0]; internal int cachedActiveCameraCount => m_CameraPool.Count; // If the key exists, we can reuse the camera. It means we are rendering the same probe/face // If it does not, it means we need a new camera. We either get one from the pool or create a new one. public Camera GetOrCreate(K key, int frameCount) { if (m_Cache == null) throw new ObjectDisposedException(nameof(ProbeCameraCache)); if (!m_Cache.TryGetValue(key, out var probeCamera) || probeCamera.camera == null || probeCamera.camera.Equals(null)) { // Key isn't currently used, we try to get an existing or new camera from the pool. if (m_CameraPool.Count == 0) { var cameraGameObject = new GameObject("Unused Probe Camera") { hideFlags = HideFlags.HideAndDontSave }; #if !UNITY_EDITOR GameObject.DontDestroyOnLoad(cameraGameObject); #endif probeCamera = (cameraGameObject.AddComponent(), frameCount); probeCamera.camera.cameraType = CameraType.Reflection; cameraGameObject.SetActive(false); } else probeCamera = (m_CameraPool.Pop(), frameCount); m_Cache[key] = probeCamera; } else { // Key already exists. Just update the current frame index. probeCamera.lastFrame = frameCount; m_Cache[key] = probeCamera; } return probeCamera.camera; } // Release unused camera keys to the pool if they are not used. // This does not clear allocations. public void ReleaseCamerasUnusedFor(int frameWindow, int frameCount) { if (m_Cache == null) throw new ObjectDisposedException(nameof(ProbeCameraCache)); if (m_Cache.Count == 0) return; // In case cameraKeysCache length does not matches the current cache length, we resize it: if (m_TempCameraKeysCache.Length != m_Cache.Count) m_TempCameraKeysCache = new K[m_Cache.Count]; // Copy keys to remove them from the dictionary (avoids collection modified while iterating error) m_Cache.Keys.CopyTo(m_TempCameraKeysCache, 0); foreach (var key in m_TempCameraKeysCache) { if (m_Cache.TryGetValue(key, out var value)) { if (Math.Abs(frameCount - value.lastFrame) > frameWindow) { if (value.camera != null) { value.camera.name = "Unused Probe Camera"; m_CameraPool.Push(value.camera); } m_Cache.Remove(key); } } } } /// Destroy all cameras in the cache and pool. public void Clear() { if (m_Cache == null) throw new ObjectDisposedException(nameof(ProbeCameraCache)); foreach (var pair in m_Cache) { if (pair.Value.camera != null) CoreUtils.Destroy(pair.Value.camera.gameObject); } m_Cache.Clear(); foreach(var camera in m_CameraPool) { if (camera != null) CoreUtils.Destroy(camera.gameObject); } m_CameraPool.Clear(); } public void Dispose() { Clear(); m_Cache = null; m_CameraPool = null; } } }