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