using System; using Unity.Mathematics; namespace UnityEngine.Rendering.Universal { // NOTE: This might become SRPCameraHistory for unified history access in the future. /// /// URP camera history texture manager. /// public class UniversalCameraHistory : ICameraHistoryReadAccess, ICameraHistoryWriteAccess, IPerFrameHistoryAccessTracker, IDisposable { /// /// Number of frames to consider history valid. /// const int k_ValidVersionCount = 2; // current frame + previous frame private static uint s_TypeCount = 0; private static class TypeId { public static uint value = s_TypeCount++; } private struct Item { public ContextItem storage; // The last tick the type was requested. public int requestVersion; // The last tick the type was written to. public int writeVersion; public void Reset() { storage?.Reset(); requestVersion = -k_ValidVersionCount; // NOTE: must be invalid on frame 0 writeVersion = -k_ValidVersionCount; } } private Item[] m_Items = new Item[32]; private int m_Version = 0; // A central storage for camera history textures. private BufferedRTHandleSystem m_HistoryTextures = new BufferedRTHandleSystem(); #region SrpApi /// /// Request access to a history item. /// /// Type of the history item. public void RequestAccess() where Type : ContextItem { uint index = TypeId.value; // Resize if(index >= m_Items.Length) { var items = new Item[math.max(math.ceilpow2(s_TypeCount), m_Items.Length * 2)]; for (var i = 0; i < m_Items.Length; i++) { items[i] = m_Items[i]; } m_Items = items; } m_Items[index].requestVersion = m_Version; } /// /// Obtain read access to a history item. /// Valid only if the item was requested and written this or the previous frame. /// /// Type of the history item. /// Instance of the history item if valid. Null otherwise. public Type GetHistoryForRead() where Type : ContextItem { uint index = TypeId.value; if (index >= m_Items.Length) return null; // If the Type wasn't written in previous or this frame, return null. // The Type design is expected to handle, current/previous/Nth frame history access via BufferedRTHandleSystem. if (!IsValid((int)index)) return null; return (Type)m_Items[index].storage; } /// /// Check if a type was requested this frame. /// /// Type of the history item. /// True if an active request exists. False otherwise. public bool IsAccessRequested() where Type : ContextItem { uint index = TypeId.value; if (index >= m_Items.Length) return false; return IsValidRequest((int)index); } /// /// Obtain write access to a history item. /// Valid only if the item was requested this or the previous frame. /// Write access implies that the contents of the history item must be written. /// /// Type of the history item. /// Instance of the history item if valid. Null otherwise. public Type GetHistoryForWrite() where Type : ContextItem, new() { uint index = TypeId.value; if (index >= m_Items.Length) return null; if (!IsValidRequest((int)index)) // If the request is too old, return null. return null; // Create Type instance on the first use if (m_Items[index].storage == null) { ref var i = ref m_Items[index]; i.storage = new Type(); // If the convenience base class for BufferedRTHandleSystem is used, set the owner. if (i.storage is CameraHistoryItem hi) hi.OnCreate(m_HistoryTextures, index); } // Assume the write for GetForWrite is done correctly by the caller. m_Items[index].writeVersion = m_Version; ContextItem item = m_Items[index].storage; return (Type)item; } /// /// Check if a type was written this frame. /// /// Type of the history item. /// True if write access was obtained this frame. False otherwise. public bool IsWritten() where Type : ContextItem { uint index = TypeId.value; if (index >= m_Items.Length) return false; return m_Items[index].writeVersion == m_Version; } /// /// Register external type request callbacks to this event. /// public event ICameraHistoryReadAccess.HistoryRequestDelegate OnGatherHistoryRequests; #endregion #region UrpApi internal UniversalCameraHistory() { // Init items with invalid versions. for(int i = 0; i < m_Items.Length; i++) m_Items[i].Reset(); } /// /// Release all camera history textures on the GPU. /// public void Dispose() { for (int i = 0; i < m_Items.Length; i++) m_Items[i].Reset(); m_HistoryTextures.ReleaseAll(); } // Query which types are needed for the registered system. internal void GatherHistoryRequests() { OnGatherHistoryRequests?.Invoke(this); } // A type is valid if it was requested this or the last frame. // Requesting access in the leaf classes, means we might be 1 frame late. private bool IsValidRequest(int i) { return ((m_Version - m_Items[i].requestVersion) < k_ValidVersionCount); } // A type is valid if it was requested and written to this or the last frame. // Requesting access in the leaf classes, means we might be 1 frame late. // NOTE: BufferedRTHandleSystem technically supports history of N length. // We might need to keep the history for N frames. // For now we expect that active history has the previous frame written. private bool IsValid(int i) { return ((m_Version - m_Items[i].writeVersion) < k_ValidVersionCount); } // Garbage collect old unused type instances and Reset them. The user is expected to free any GPU resources. internal void ReleaseUnusedHistory() { for (int i = 0; i < m_Items.Length; i++) { if (!IsValidRequest(i) && !IsValid(i)) m_Items[i].Reset(); } // After collecting stale Types, start a new generation. m_Version++; } // Set the camera reference size for all history textures. internal void SwapAndSetReferenceSize(int cameraWidth, int cameraHeight) { m_HistoryTextures.SwapAndSetReferenceSize(cameraWidth, cameraHeight); } #endregion } }