This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
using UnityEngine.Events;
namespace UnityEngine.Rendering
{
/// <summary>
/// Command Buffer Pool
/// </summary>
public static class CommandBufferPool
{
static ObjectPool<CommandBuffer> s_BufferPool = new ObjectPool<CommandBuffer>(null, x => x.Clear());
/// <summary>
/// Get a new Command Buffer.
/// </summary>
/// <returns></returns>
public static CommandBuffer Get()
{
var cmd = s_BufferPool.Get();
// Set to empty on purpose, does not create profiling markers.
cmd.name = "";
return cmd;
}
/// <summary>
/// Get a new Command Buffer and assign a name to it.
/// Named Command Buffers will add profiling makers implicitly for the buffer execution.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static CommandBuffer Get(string name)
{
var cmd = s_BufferPool.Get();
cmd.name = name;
return cmd;
}
/// <summary>
/// Release a Command Buffer.
/// </summary>
/// <param name="buffer"></param>
public static void Release(CommandBuffer buffer)
{
s_BufferPool.Release(buffer);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Render Textures clear flag.
/// </summary>
[Flags]
public enum ClearFlag
{
/// <summary>Don't clear.</summary>
None = 0,
/// <summary>Clear the color buffer.</summary>
Color = 1,
/// <summary>Clear the depth buffer.</summary>
Depth = 2,
/// <summary>Clear both color and depth buffers.</summary>
All = Depth | Color
}
}

View File

@@ -0,0 +1,45 @@
namespace UnityEngine.Rendering
{
// Use this class to get a static instance of a component
// Mainly used to have a default instance
/// <summary>
/// Singleton of a Component class.
/// </summary>
/// <typeparam name="TType">Component type.</typeparam>
public static class ComponentSingleton<TType>
where TType : Component
{
static TType s_Instance = null;
/// <summary>
/// Instance of the required component type.
/// </summary>
public static TType instance
{
get
{
if (s_Instance == null)
{
GameObject go = new GameObject("Default " + typeof(TType).Name) { hideFlags = HideFlags.HideAndDontSave };
go.SetActive(false);
s_Instance = go.AddComponent<TType>();
}
return s_Instance;
}
}
/// <summary>
/// Release the component singleton.
/// </summary>
public static void Release()
{
if (s_Instance != null)
{
var go = s_Instance.gameObject;
CoreUtils.Destroy(go);
s_Instance = null;
}
}
}
}

View File

@@ -0,0 +1,269 @@
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering
{
/// <summary>
/// Constant Buffer management class.
/// </summary>
public class ConstantBuffer
{
static List<ConstantBufferBase> m_RegisteredConstantBuffers = new List<ConstantBufferBase>();
/// <summary>
/// Update the GPU data of the constant buffer and bind it globally.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void PushGlobal<CBType>(CommandBuffer cmd, in CBType data, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(cmd, data);
cb.SetGlobal(cmd, shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer and bind it to a compute shader.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
/// <param name="cs">Compute shader to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void Push<CBType>(CommandBuffer cmd, in CBType data, ComputeShader cs, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(cmd, data);
cb.Set(cmd, cs, shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer and bind it to a material.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
/// <param name="mat">Material to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void Push<CBType>(CommandBuffer cmd, in CBType data, Material mat, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(cmd, data);
cb.Set(mat, shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
public static void UpdateData<CBType>(CommandBuffer cmd, in CBType data) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(cmd, data);
}
/// <summary>
/// Bind the constant buffer globally.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void SetGlobal<CBType>(CommandBuffer cmd, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.SetGlobal(cmd, shaderId);
}
/// <summary>
/// Bind the constant buffer to a compute shader.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="cs">Compute shader to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void Set<CBType>(CommandBuffer cmd, ComputeShader cs, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.Set(cmd, cs, shaderId);
}
/// <summary>
/// Bind the constant buffer to a material.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="mat">Material to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void Set<CBType>(Material mat, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.Set(mat, shaderId);
}
/// <summary>
/// Release all currently allocated singleton constant buffers.
/// This needs to be called before shutting down the application.
/// </summary>
public static void ReleaseAll()
{
foreach (var cb in m_RegisteredConstantBuffers)
cb.Release();
m_RegisteredConstantBuffers.Clear();
}
internal static void Register(ConstantBufferBase cb)
{
m_RegisteredConstantBuffers.Add(cb);
}
}
/// <summary>
/// The base class of Constant Buffer.
/// </summary>
public abstract class ConstantBufferBase
{
/// <summary>
/// Release the constant buffer.
/// </summary>
public abstract void Release();
}
/// <summary>
/// An instance of a constant buffer.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
public class ConstantBuffer<CBType> : ConstantBufferBase where CBType : struct
{
// Used to track all global bindings used by this CB type.
HashSet<int> m_GlobalBindings = new HashSet<int>();
// Array is required by the ComputeBuffer SetData API
CBType[] m_Data = new CBType[1];
ComputeBuffer m_GPUConstantBuffer = null;
/// <summary>
/// Constant Buffer constructor.
/// </summary>
public ConstantBuffer()
{
m_GPUConstantBuffer = new ComputeBuffer(1, UnsafeUtility.SizeOf<CBType>(), ComputeBufferType.Constant);
}
/// <summary>
/// Update the GPU data of the constant buffer.
/// </summary>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
public void UpdateData(CommandBuffer cmd, in CBType data)
{
m_Data[0] = data;
#if UNITY_2021_1_OR_NEWER
cmd.SetBufferData(m_GPUConstantBuffer, m_Data);
#else
cmd.SetComputeBufferData(m_GPUConstantBuffer, m_Data);
#endif
}
/// <summary>
/// Bind the constant buffer globally.
/// </summary>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public void SetGlobal(CommandBuffer cmd, int shaderId)
{
m_GlobalBindings.Add(shaderId);
cmd.SetGlobalConstantBuffer(m_GPUConstantBuffer, shaderId, 0, m_GPUConstantBuffer.stride);
}
/// <summary>
/// Bind the constant buffer to a compute shader.
/// </summary>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="cs">Compute shader to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public void Set(CommandBuffer cmd, ComputeShader cs, int shaderId)
{
cmd.SetComputeConstantBufferParam(cs, shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride);
}
/// <summary>
/// Bind the constant buffer to a material.
/// </summary>
/// <param name="mat">Material to which the constant buffer should be bound.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public void Set(Material mat, int shaderId)
{
// This isn't done via command buffer because as long as the buffer itself is not destroyed,
// the binding stays valid. Only the commit of data needs to go through the command buffer.
// We do it here anyway for now to simplify user API.
mat.SetConstantBuffer(shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride);
}
/// <summary>
/// Update the GPU data of the constant buffer and bind it globally.
/// </summary>
/// <param name="cmd">Command Buffer used to execute the graphic commands.</param>
/// <param name="data">Input data of the constant buffer.</param>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public void PushGlobal(CommandBuffer cmd, in CBType data, int shaderId)
{
UpdateData(cmd, data);
SetGlobal(cmd, shaderId);
}
/// <summary>
/// Release the constant buffers.
/// </summary>
public override void Release()
{
// Depending on the device, globally bound buffers can leave stale "valid" shader ids pointing to a destroyed buffer.
// In DX11 it does not cause issues but on Vulkan this will result in skipped drawcalls (even if the buffer is not actually accessed in the shader).
// To avoid this kind of issues, it's good practice to "unbind" all globally bound buffers upon destruction.
foreach (int shaderId in m_GlobalBindings)
Shader.SetGlobalConstantBuffer(shaderId, (ComputeBuffer)null, 0, 0);
m_GlobalBindings.Clear();
CoreUtils.SafeRelease(m_GPUConstantBuffer);
}
}
class ConstantBufferSingleton<CBType> : ConstantBuffer<CBType> where CBType : struct
{
static ConstantBufferSingleton<CBType> s_Instance = null;
internal static ConstantBufferSingleton<CBType> instance
{
get
{
if (s_Instance == null)
{
s_Instance = new ConstantBufferSingleton<CBType>();
ConstantBuffer.Register(s_Instance);
}
return s_Instance;
}
set
{
s_Instance = value;
}
}
public override void Release()
{
base.Release();
s_Instance = null;
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Attribute used to customize UI display.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class DisplayInfoAttribute : Attribute
{
/// <summary>Display name used in UI.</summary>
public string name;
/// <summary>Display order used in UI.</summary>
public int order;
}
/// <summary>
/// Attribute used to customize UI display to allow properties only be visible when "Show Additional Properties" is selected
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class AdditionalPropertyAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,488 @@
using System;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering
{
/// <summary>
/// Static class with unsafe utility functions.
/// </summary>
public static unsafe class CoreUnsafeUtils
{
/// <summary>
/// Fixed Buffer String Queue class.
/// </summary>
public struct FixedBufferStringQueue
{
byte* m_ReadCursor;
byte* m_WriteCursor;
readonly byte* m_BufferEnd;
readonly byte* m_BufferStart;
readonly int m_BufferLength;
/// <summary>
/// Number of element in the queue.
/// </summary>
public int Count { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ptr">Buffer pointer.</param>
/// <param name="length">Length of the provided allocated buffer in byte.</param>
public FixedBufferStringQueue(byte* ptr, int length)
{
m_BufferStart = ptr;
m_BufferLength = length;
m_BufferEnd = m_BufferStart + m_BufferLength;
m_ReadCursor = m_BufferStart;
m_WriteCursor = m_BufferStart;
Count = 0;
Clear();
}
/// <summary>
/// Try to push a new element in the queue.
/// </summary>
/// <param name="v">Element to push in the queue.</param>
/// <returns>True if the new element could be pushed in the queue. False if reserved memory was not enough.</returns>
public bool TryPush(string v)
{
var size = v.Length * sizeof(char) + sizeof(int);
if (m_WriteCursor + size >= m_BufferEnd)
return false;
*(int*)m_WriteCursor = v.Length;
m_WriteCursor += sizeof(int);
var charPtr = (char*)m_WriteCursor;
for (int i = 0; i < v.Length; ++i, ++charPtr)
*charPtr = v[i];
m_WriteCursor += sizeof(char) * v.Length;
++Count;
return true;
}
/// <summary>
/// Pop an element of the queue.
/// </summary>
/// <param name="v">Output result string.</param>
/// <returns>True if an element was succesfuly poped.</returns>
public bool TryPop(out string v)
{
var size = *(int*)m_ReadCursor;
if (size != 0)
{
m_ReadCursor += sizeof(int);
v = new string((char*)m_ReadCursor, 0, size);
m_ReadCursor += size * sizeof(char);
return true;
}
v = default;
return false;
}
/// <summary>
/// Clear the queue.
/// </summary>
public void Clear()
{
m_WriteCursor = m_BufferStart;
m_ReadCursor = m_BufferStart;
Count = 0;
UnsafeUtility.MemClear(m_BufferStart, m_BufferLength);
}
}
/// <summary>
/// Key Getter interface.
/// </summary>
/// <typeparam name="TValue">Value</typeparam>
/// <typeparam name="TKey">Key</typeparam>
public interface IKeyGetter<TValue, TKey>
{
/// <summary>Getter</summary>
/// <param name="v">The value</param>
/// <returns>The key</returns>
TKey Get(ref TValue v);
}
internal struct DefaultKeyGetter<T> : IKeyGetter<T, T>
{ public T Get(ref T v) { return v; } }
// Note: this is a workaround needed to circumvent some AOT issues when building for xbox
internal struct UintKeyGetter : IKeyGetter<uint, uint>
{ public uint Get(ref uint v) { return v; } }
/// <summary>
/// Extension method to copy elements of a list into a buffer.
/// </summary>
/// <typeparam name="T">Type of the provided List.</typeparam>
/// <param name="list">Input List.</param>
/// <param name="dest">Destination buffer.</param>
/// <param name="count">Number of elements to copy.</param>
public static void CopyTo<T>(this List<T> list, void* dest, int count)
where T : struct
{
var c = Mathf.Min(count, list.Count);
for (int i = 0; i < c; ++i)
UnsafeUtility.WriteArrayElement<T>(dest, i, list[i]);
}
/// <summary>
/// Extension method to copy elements of an array into a buffer.
/// </summary>
/// <typeparam name="T">Type of the provided array.</typeparam>
/// <param name="list">Input List.</param>
/// <param name="dest">Destination buffer.</param>
/// <param name="count">Number of elements to copy.</param>
public static void CopyTo<T>(this T[] list, void* dest, int count)
where T : struct
{
var c = Mathf.Min(count, list.Length);
for (int i = 0; i < c; ++i)
UnsafeUtility.WriteArrayElement<T>(dest, i, list[i]);
}
/// <summary>
/// Quick Sort
/// </summary>
/// <param name="arr">uint array.</param>
/// <param name="left">Left boundary.</param>
/// <param name="right">Left boundary.</param>
public static unsafe void QuickSort(uint[] arr, int left, int right)
{
fixed(uint* ptr = arr)
CoreUnsafeUtils.QuickSort<uint, uint, UintKeyGetter>(ptr, left, right);
}
/// <summary>
/// Quick sort.
/// </summary>
/// <typeparam name="T">Type to compare.</typeparam>
/// <param name="count">Number of element.</param>
/// <param name="data">Buffer to sort.</param>
public static void QuickSort<T>(int count, void* data)
where T : struct, IComparable<T>
{
QuickSort<T, T, DefaultKeyGetter<T>>(data, 0, count - 1);
}
/// <summary>
/// Quick sort.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
/// <typeparam name="TKey">Key Type.</typeparam>
/// <typeparam name="TGetter">Getter type.</typeparam>
/// <param name="count">Number of element.</param>
/// <param name="data">Data to sort.</param>
public static void QuickSort<TValue, TKey, TGetter>(int count, void* data)
where TKey : struct, IComparable<TKey>
where TValue : struct
where TGetter : struct, IKeyGetter<TValue, TKey>
{
QuickSort<TValue, TKey, TGetter>(data, 0, count - 1);
}
/// <summary>
/// Quick sort.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
/// <typeparam name="TKey">Key Type.</typeparam>
/// <typeparam name="TGetter">Getter type.</typeparam>
/// <param name="data">Data to sort.</param>
/// <param name="left">Left boundary.</param>
/// <param name="right">Right boundary.</param>
public static void QuickSort<TValue, TKey, TGetter>(void* data, int left, int right)
where TKey : struct, IComparable<TKey>
where TValue : struct
where TGetter : struct, IKeyGetter<TValue, TKey>
{
// For Recursion
if (left < right)
{
int pivot = Partition<TValue, TKey, TGetter>(data, left, right);
if (pivot >= 1)
QuickSort<TValue, TKey, TGetter>(data, left, pivot);
if (pivot + 1 < right)
QuickSort<TValue, TKey, TGetter>(data, pivot + 1, right);
}
}
/// <summary>
/// Index of an element in a buffer.
/// </summary>
/// <typeparam name="T">Data type.</typeparam>
/// <param name="data">Data buffer.</param>
/// <param name="count">Number of elements.</param>
/// <param name="v">Element to test against.</param>
/// <returns>The first index of the provided element.</returns>
public static int IndexOf<T>(void* data, int count, T v)
where T : struct, IEquatable<T>
{
for (int i = 0; i < count; ++i)
{
if (UnsafeUtility.ReadArrayElement<T>(data, i).Equals(v))
return i;
}
return -1;
}
/// <summary>
/// Compare hashes of two collections and provide
/// a list of indices <paramref name="removeIndices"/> to remove in <paramref name="oldHashes"/>
/// and a list of indices <paramref name="addIndices"/> to add in <paramref name="newHashes"/>.
///
/// Assumes that <paramref name="newHashes"/> and <paramref name="oldHashes"/> are sorted.
/// </summary>
/// <typeparam name="TOldValue">Old value type.</typeparam>
/// <typeparam name="TOldGetter">Old getter type.</typeparam>
/// <typeparam name="TNewValue">New value type.</typeparam>
/// <typeparam name="TNewGetter">New getter type.</typeparam>
/// <param name="oldHashCount">Number of hashes in <paramref name="oldHashes"/>.</param>
/// <param name="oldHashes">Previous hashes to compare.</param>
/// <param name="newHashCount">Number of hashes in <paramref name="newHashes"/>.</param>
/// <param name="newHashes">New hashes to compare.</param>
/// <param name="addIndices">Indices of element to add in <paramref name="newHashes"/> will be written here.</param>
/// <param name="removeIndices">Indices of element to remove in <paramref name="oldHashes"/> will be written here.</param>
/// <param name="addCount">Number of elements to add will be written here.</param>
/// <param name="remCount">Number of elements to remove will be written here.</param>
/// <returns>The number of operation to perform (<code><paramref name="addCount"/> + <paramref name="remCount"/></code>)</returns>
public static int CompareHashes<TOldValue, TOldGetter, TNewValue, TNewGetter>(
int oldHashCount, void* oldHashes,
int newHashCount, void* newHashes,
// assume that the capacity of indices is >= max(oldHashCount, newHashCount)
int* addIndices, int* removeIndices,
out int addCount, out int remCount
)
where TOldValue : struct
where TNewValue : struct
where TOldGetter : struct, IKeyGetter<TOldValue, Hash128>
where TNewGetter : struct, IKeyGetter<TNewValue, Hash128>
{
var oldGetter = new TOldGetter();
var newGetter = new TNewGetter();
addCount = 0;
remCount = 0;
// Check combined hashes
if (oldHashCount == newHashCount)
{
var oldHash = new Hash128();
var newHash = new Hash128();
CombineHashes<TOldValue, TOldGetter>(oldHashCount, oldHashes, &oldHash);
CombineHashes<TNewValue, TNewGetter>(newHashCount, newHashes, &newHash);
if (oldHash == newHash)
return 0;
}
var numOperations = 0;
var oldI = 0;
var newI = 0;
while (oldI < oldHashCount || newI < newHashCount)
{
// At the end of old array.
if (oldI == oldHashCount)
{
// No more hashes in old array. Add remaining entries from new array.
for (; newI < newHashCount; ++newI)
{
addIndices[addCount++] = newI;
++numOperations;
}
continue;
}
// At end of new array.
if (newI == newHashCount)
{
// No more hashes in old array. Remove remaining entries from old array.
for (; oldI < oldHashCount; ++oldI)
{
removeIndices[remCount++] = oldI;
++numOperations;
}
continue;
}
// Both arrays have data.
var newVal = UnsafeUtility.ReadArrayElement<TNewValue>(newHashes, newI);
var oldVal = UnsafeUtility.ReadArrayElement<TOldValue>(oldHashes, oldI);
var newKey = newGetter.Get(ref newVal);
var oldKey = oldGetter.Get(ref oldVal);
if (newKey == oldKey)
{
// Matching hash, skip.
++newI;
++oldI;
continue;
}
// Both arrays have data, but hashes do not match.
if (newKey < oldKey)
{
// oldIter is the greater hash. Push "add" jobs from the new array until reaching the oldIter hash.
while (newI < newHashCount && newKey < oldKey)
{
addIndices[addCount++] = newI;
++newI;
++numOperations;
newVal = UnsafeUtility.ReadArrayElement<TNewValue>(newHashes, newI);
newKey = newGetter.Get(ref newVal);
}
}
else
{
// newIter is the greater hash. Push "remove" jobs from the old array until reaching the newIter hash.
while (oldI < oldHashCount && oldKey < newKey)
{
removeIndices[remCount++] = oldI;
++numOperations;
++oldI;
}
}
}
return numOperations;
}
/// <summary>
/// Compare hashes.
/// </summary>
/// <param name="oldHashCount">Number of hashes in <paramref name="oldHashes"/>.</param>
/// <param name="oldHashes">Previous hashes to compare.</param>
/// <param name="newHashCount">Number of hashes in <paramref name="newHashes"/>.</param>
/// <param name="newHashes">New hashes to compare.</param>
/// <param name="addIndices">Indices of element to add in <paramref name="newHashes"/> will be written here.</param>
/// <param name="removeIndices">Indices of element to remove in <paramref name="oldHashes"/> will be written here.</param>
/// <param name="addCount">Number of elements to add will be written here.</param>
/// <param name="remCount">Number of elements to remove will be written here.</param>
/// <returns>The number of operation to perform (<code><paramref name="addCount"/> + <paramref name="remCount"/></code>)</returns>
public static int CompareHashes(
int oldHashCount, Hash128* oldHashes,
int newHashCount, Hash128* newHashes,
// assume that the capacity of indices is >= max(oldHashCount, newHashCount)
int* addIndices, int* removeIndices,
out int addCount, out int remCount
)
{
return CompareHashes<Hash128, DefaultKeyGetter<Hash128>, Hash128, DefaultKeyGetter<Hash128>>(
oldHashCount, oldHashes,
newHashCount, newHashes,
addIndices, removeIndices,
out addCount, out remCount
);
}
/// <summary>Combine all of the hashes of a collection of hashes.</summary>
/// <typeparam name="TValue">Value type.</typeparam>
/// <typeparam name="TGetter">Getter type.</typeparam>
/// <param name="count">Number of hash to combine.</param>
/// <param name="hashes">Hashes to combine.</param>
/// <param name="outHash">Hash to update.</param>
public static void CombineHashes<TValue, TGetter>(int count, void* hashes, Hash128* outHash)
where TValue : struct
where TGetter : struct, IKeyGetter<TValue, Hash128>
{
var getter = new TGetter();
for (int i = 0; i < count; ++i)
{
var v = UnsafeUtility.ReadArrayElement<TValue>(hashes, i);
var h = getter.Get(ref v);
HashUtilities.AppendHash(ref h, ref *outHash);
}
}
/// <summary>
/// Combine hashes.
/// </summary>
/// <param name="count">Number of hash to combine.</param>
/// <param name="hashes">Hashes to combine.</param>
/// <param name="outHash">Hash to update.</param>
public static void CombineHashes(int count, Hash128* hashes, Hash128* outHash)
{
CombineHashes<Hash128, DefaultKeyGetter<Hash128>>(count, hashes, outHash);
}
// Just a sort function that doesn't allocate memory
// Note: Should be replace by a radix sort for positive integer
static int Partition<TValue, TKey, TGetter>(void* data, int left, int right)
where TKey : struct, IComparable<TKey>
where TValue : struct
where TGetter : struct, IKeyGetter<TValue, TKey>
{
var getter = default(TGetter);
var pivotvalue = UnsafeUtility.ReadArrayElement<TValue>(data, left);
var pivot = getter.Get(ref pivotvalue);
--left;
++right;
while (true)
{
var c = 0;
var lvalue = default(TValue);
var lkey = default(TKey);
do
{
++left;
lvalue = UnsafeUtility.ReadArrayElement<TValue>(data, left);
lkey = getter.Get(ref lvalue);
c = lkey.CompareTo(pivot);
}
while (c < 0);
var rvalue = default(TValue);
var rkey = default(TKey);
do
{
--right;
rvalue = UnsafeUtility.ReadArrayElement<TValue>(data, right);
rkey = getter.Get(ref rvalue);
c = rkey.CompareTo(pivot);
}
while (c > 0);
if (left < right)
{
UnsafeUtility.WriteArrayElement(data, right, lvalue);
UnsafeUtility.WriteArrayElement(data, left, rvalue);
}
else
{
return right;
}
}
}
/// <summary>
/// Checks for duplicates in an array.
/// </summary>
/// <param name="arr">Input array.</param>
/// <returns>True if there is any duplicate in the input array.</returns>
public static unsafe bool HaveDuplicates(int[] arr)
{
int* copy = stackalloc int[arr.Length];
arr.CopyTo<int>(copy, arr.Length);
QuickSort<int>(arr.Length, copy);
for (int i = arr.Length - 1; i > 0; --i)
{
if (UnsafeUtility.ReadArrayElement<int>(copy, i).CompareTo(UnsafeUtility.ReadArrayElement<int>(copy, i - 1)) == 0)
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Generic growable array.
/// </summary>
/// <typeparam name="T">Type of the array.</typeparam>
public class DynamicArray<T> where T : new()
{
T[] m_Array = null;
/// <summary>
/// Number of elements in the array.
/// </summary>
public int size { get; private set; }
/// <summary>
/// Allocated size of the array.
/// </summary>
public int capacity { get { return m_Array.Length; } }
/// <summary>
/// Constructor.
/// Defaults to a size of 32 elements.
/// </summary>
public DynamicArray()
{
m_Array = new T[32];
size = 0;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="size">Number of elements.</param>
public DynamicArray(int size)
{
m_Array = new T[size];
this.size = size;
}
/// <summary>
/// Clear the array of all elements.
/// </summary>
public void Clear()
{
size = 0;
}
/// <summary>
/// Add an element to the array.
/// </summary>
/// <param name="value">Element to add to the array.</param>
/// <returns>The index of the element.</returns>
public int Add(in T value)
{
int index = size;
// Grow array if needed;
if (index >= m_Array.Length)
{
var newArray = new T[m_Array.Length * 2];
Array.Copy(m_Array, newArray, m_Array.Length);
m_Array = newArray;
}
m_Array[index] = value;
size++;
return index;
}
/// <summary>
/// Resize the Dynamic Array.
/// This will reallocate memory if necessary and set the current size of the array to the provided size.
/// </summary>
/// <param name="newSize">New size for the array.</param>
/// <param name="keepContent">Set to true if you want the current content of the array to be kept.</param>
public void Resize(int newSize, bool keepContent = false)
{
if (newSize > m_Array.Length)
{
if (keepContent)
{
var newArray = new T[newSize];
Array.Copy(m_Array, newArray, m_Array.Length);
m_Array = newArray;
}
else
{
m_Array = new T[newSize];
}
}
size = newSize;
}
/// <summary>
/// ref access to an element.
/// </summary>
/// <param name="index">Element index</param>
/// <returns>The requested element.</returns>
public ref T this[int index]
{
get
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (index >= size)
throw new IndexOutOfRangeException();
#endif
return ref m_Array[index];
}
}
}
}

View File

@@ -0,0 +1,440 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// The format of the delegate used to perofrm dynamic resolution.
/// </summary>
public delegate float PerformDynamicRes();
/// <summary>
/// The type of dynamic resolution scaler. It essentially defines what the output of the scaler is expected to be.
/// </summary>
public enum DynamicResScalePolicyType
{
/// <summary>
/// If is the option, DynamicResolutionHandler expects the scaler to return a screen percentage.
/// The value set will be clamped between the minimum and maximum percentage set in the GlobalDynamicResolutionSettings.
/// </summary>
ReturnsPercentage,
/// <summary>
/// If is the option, DynamicResolutionHandler expects the scaler to return a factor t in the [0..1] such that the final resolution percentage
/// is determined by lerp(minimumPercentage, maximumPercentage, t), where the minimum and maximum percentages are the one set in the GlobalDynamicResolutionSettings.
/// </summary>
ReturnsMinMaxLerpFactor
}
/// <summary>
/// The class responsible to handle dynamic resolution.
/// </summary>
public class DynamicResolutionHandler
{
private bool m_Enabled;
private float m_MinScreenFraction;
private float m_MaxScreenFraction;
private float m_CurrentFraction;
private bool m_ForcingRes;
private bool m_CurrentCameraRequest;
private float m_PrevFraction;
private bool m_ForceSoftwareFallback;
private float m_PrevHWScaleWidth;
private float m_PrevHWScaleHeight;
private Vector2Int m_LastScaledSize;
private void Reset()
{
m_Enabled = false;
m_MinScreenFraction = 1.0f;
m_MaxScreenFraction = 1.0f;
m_CurrentFraction = 1.0f;
m_ForcingRes = false;
m_CurrentCameraRequest = true;
m_PrevFraction = -1.0f;
m_ForceSoftwareFallback = false;
m_PrevHWScaleWidth = 1.0f;
m_PrevHWScaleHeight = 1.0f;
m_LastScaledSize = new Vector2Int(0, 0);
filter = DynamicResUpscaleFilter.Bilinear;
}
private static DynamicResScalePolicyType s_ScalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor;
private static PerformDynamicRes s_DynamicResMethod = DefaultDynamicResMethod;
// Debug
private Vector2Int cachedOriginalSize;
/// <summary>
/// The filter that is used to upscale the rendering result to the native resolution.
/// </summary>
public DynamicResUpscaleFilter filter { get; set; }
/// <summary>
/// The viewport of the final buffer. This is likely the resolution the dynamic resolution starts from before any scaling. Note this is NOT the target resolution the rendering will happen in
/// but the resolution the scaled rendered result will be upscaled to.
/// </summary>
public Vector2Int finalViewport { get; set; }
private DynamicResolutionType type;
private GlobalDynamicResolutionSettings m_CachedSettings = GlobalDynamicResolutionSettings.NewDefault();
private const int CameraDictionaryMaxcCapacity = 32;
private WeakReference m_OwnerCameraWeakRef = null;
private static Dictionary<int, DynamicResolutionHandler> s_CameraInstances = new Dictionary<int, DynamicResolutionHandler>(CameraDictionaryMaxcCapacity);
private static DynamicResolutionHandler s_DefaultInstance = new DynamicResolutionHandler();
private static int s_ActiveCameraId = 0;
private static DynamicResolutionHandler s_ActiveInstance = s_DefaultInstance;
//private global state of ScalableBufferManager
private static bool s_ActiveInstanceDirty = true;
private static float s_GlobalHwFraction = 1.0f;
private static bool s_GlobalHwUpresActive = false;
private bool FlushScalableBufferManagerState()
{
if (s_GlobalHwUpresActive == HardwareDynamicResIsEnabled() && s_GlobalHwFraction == m_CurrentFraction)
return false;
s_GlobalHwUpresActive = HardwareDynamicResIsEnabled();
s_GlobalHwFraction = m_CurrentFraction;
ScalableBufferManager.ResizeBuffers(s_GlobalHwFraction, s_GlobalHwFraction);
return true;
}
private static DynamicResolutionHandler GetOrCreateDrsInstanceHandler(Camera camera)
{
if (camera == null)
return null;
DynamicResolutionHandler instance = null;
var key = camera.GetInstanceID();
if (!s_CameraInstances.TryGetValue(key, out instance))
{
//if this camera is not available in the map of cameras lets try creating one.
//first and foremost, if we exceed the dictionary capacity, lets try and recycle an object that is dead.
if (s_CameraInstances.Count >= CameraDictionaryMaxcCapacity)
{
int recycledInstanceKey = 0;
DynamicResolutionHandler recycledInstance = null;
foreach (var kv in s_CameraInstances)
{
//is this object dead? that is, belongs to a camera that was destroyed?
if (kv.Value.m_OwnerCameraWeakRef == null || !kv.Value.m_OwnerCameraWeakRef.IsAlive)
{
recycledInstance = kv.Value;
recycledInstanceKey = kv.Key;
break;
}
}
if (recycledInstance != null)
{
instance = recycledInstance;
s_CameraInstances.Remove(recycledInstanceKey);
}
}
//if we didnt find a dead object, we create one from scratch.
if (instance == null)
{
instance = new DynamicResolutionHandler();
instance.m_OwnerCameraWeakRef = new WeakReference(camera);
}
else
{
//otherwise, we found a dead object, lets reset it, and have a weak ref to this camera,
//so we can possibly recycle it in the future by checking the camera's weak pointer state.
instance.Reset();
instance.m_OwnerCameraWeakRef.Target = camera;
}
s_CameraInstances.Add(key, instance);
}
return instance;
}
/// <summary>
/// Get the instance of the global dynamic resolution handler.
/// </summary>
public static DynamicResolutionHandler instance { get { return s_ActiveInstance; } }
private DynamicResolutionHandler()
{
Reset();
}
// TODO: Eventually we will need to provide a good default implementation for this.
static private float DefaultDynamicResMethod()
{
return 1.0f;
}
private void ProcessSettings(GlobalDynamicResolutionSettings settings)
{
m_Enabled = settings.enabled && (Application.isPlaying || settings.forceResolution);
if (!m_Enabled)
{
m_CurrentFraction = 1.0f;
}
else
{
type = settings.dynResType;
float minScreenFrac = Mathf.Clamp(settings.minPercentage / 100.0f, 0.1f, 1.0f);
m_MinScreenFraction = minScreenFrac;
float maxScreenFrac = Mathf.Clamp(settings.maxPercentage / 100.0f, m_MinScreenFraction, 3.0f);
m_MaxScreenFraction = maxScreenFrac;
filter = settings.upsampleFilter;
m_ForcingRes = settings.forceResolution;
if (m_ForcingRes)
{
float fraction = Mathf.Clamp(settings.forcedPercentage / 100.0f, 0.1f, 1.5f);
m_CurrentFraction = fraction;
}
}
m_CachedSettings = settings;
}
public Vector2 GetResolvedScale()
{
if (!m_Enabled || !m_CurrentCameraRequest)
{
return new Vector2(1.0f, 1.0f);
}
float scaleFractionX = m_CurrentFraction;
float scaleFractionY = m_CurrentFraction;
if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware)
{
scaleFractionX = ScalableBufferManager.widthScaleFactor;
scaleFractionY = ScalableBufferManager.heightScaleFactor;
}
return new Vector2(scaleFractionX, scaleFractionY);
}
/// <summary>
/// Set the scaler method used to drive dynamic resolution.
/// </summary>
/// <param name="scaler">The delegate used to determine the resolution percentage used by the dynamic resolution system.</param>
/// <param name="scalerType">The type of scaler that is used, this is used to indicate the return type of the scaler to the dynamic resolution system.</param>
static public void SetDynamicResScaler(PerformDynamicRes scaler, DynamicResScalePolicyType scalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor)
{
s_ScalerType = scalerType;
s_DynamicResMethod = scaler;
}
/// <summary>
/// Will clear the currently used camera. Use this function to restore the default instance when UpdateAndUseCamera is called.
/// </summary>
public static void ClearSelectedCamera()
{
s_ActiveInstance = s_DefaultInstance;
s_ActiveCameraId = 0;
s_ActiveInstanceDirty = true;
}
/// <summary>
/// Set whether the camera that is currently processed by the pipeline has requested dynamic resolution or not.
/// </summary>
/// <param name="cameraRequest">Determines whether the camera has requested dynamic resolution or not.</param>
public void SetCurrentCameraRequest(bool cameraRequest)
{
m_CurrentCameraRequest = cameraRequest;
}
/// <summary>
/// Update the state of the dynamic resolution system for a specific camera.
/// Call this function also to switch context between cameras (will set the current camera as active).
// Passing a null camera has the same effect as calling Update without the camera parameter.
/// </summary>
/// <param name="camera">Camera used to select a specific instance tied to this DynamicResolutionHandler instance.
/// </param>
/// <param name="settings">(optional) The settings that are to be used by the dynamic resolution system. passing null for the settings will result in the last update's settings used.</param>
/// <param name="OnResolutionChange">An action that will be called every time the dynamic resolution system triggers a change in resolution.</param>
public static void UpdateAndUseCamera(Camera camera, GlobalDynamicResolutionSettings? settings = null, Action OnResolutionChange = null)
{
int newCameraId;
if (camera == null)
{
s_ActiveInstance = s_DefaultInstance;
newCameraId = 0;
}
else
{
s_ActiveInstance = GetOrCreateDrsInstanceHandler(camera);
newCameraId = camera.GetInstanceID();
}
s_ActiveInstanceDirty = newCameraId != s_ActiveCameraId;
s_ActiveCameraId = newCameraId;
s_ActiveInstance.Update(settings.HasValue ? settings.Value : s_ActiveInstance.m_CachedSettings, OnResolutionChange);
}
/// <summary>
/// Update the state of the dynamic resolution system.
/// </summary>
/// <param name="settings">The settings that are to be used by the dynamic resolution system.</param>
/// <param name="OnResolutionChange">An action that will be called every time the dynamic resolution system triggers a change in resolution.</param>
public void Update(GlobalDynamicResolutionSettings settings, Action OnResolutionChange = null)
{
ProcessSettings(settings);
if (!m_Enabled && !s_ActiveInstanceDirty)
{
s_ActiveInstanceDirty = false;
return;
}
if (!m_ForcingRes)
{
if (s_ScalerType == DynamicResScalePolicyType.ReturnsMinMaxLerpFactor)
{
float currLerp = s_DynamicResMethod();
float lerpFactor = Mathf.Clamp(currLerp, 0.0f, 1.0f);
m_CurrentFraction = Mathf.Lerp(m_MinScreenFraction, m_MaxScreenFraction, lerpFactor);
}
else if (s_ScalerType == DynamicResScalePolicyType.ReturnsPercentage)
{
float percentageRequested = Mathf.Max(s_DynamicResMethod(), 5.0f);
m_CurrentFraction = Mathf.Clamp(percentageRequested / 100.0f, m_MinScreenFraction, m_MaxScreenFraction);
}
}
bool hardwareResolutionChanged = false;
bool softwareResolutionChanged = m_CurrentFraction != m_PrevFraction;
m_PrevFraction = m_CurrentFraction;
if (!m_ForceSoftwareFallback && type == DynamicResolutionType.Hardware)
{
hardwareResolutionChanged = FlushScalableBufferManagerState();
if (ScalableBufferManager.widthScaleFactor != m_PrevHWScaleWidth ||
ScalableBufferManager.heightScaleFactor != m_PrevHWScaleHeight)
{
hardwareResolutionChanged = true;
}
}
if ((softwareResolutionChanged || hardwareResolutionChanged) && OnResolutionChange != null)
OnResolutionChange();
s_ActiveInstanceDirty = false;
m_PrevHWScaleWidth = ScalableBufferManager.widthScaleFactor;
m_PrevHWScaleHeight = ScalableBufferManager.heightScaleFactor;
}
/// <summary>
/// Determines whether software dynamic resolution is enabled or not.
/// </summary>
/// <returns>True: Software dynamic resolution is enabled</returns>
public bool SoftwareDynamicResIsEnabled()
{
return m_CurrentCameraRequest && m_Enabled && m_CurrentFraction != 1.0f && (m_ForceSoftwareFallback || type == DynamicResolutionType.Software);
}
/// <summary>
/// Determines whether hardware dynamic resolution is enabled or not.
/// </summary>
/// <returns>True: Hardware dynamic resolution is enabled</returns>
public bool HardwareDynamicResIsEnabled()
{
return !m_ForceSoftwareFallback && m_CurrentCameraRequest && m_Enabled && type == DynamicResolutionType.Hardware;
}
/// <summary>
/// Identifies whether hardware dynamic resolution has been requested and is going to be used.
/// </summary>
/// <returns>True: Hardware dynamic resolution is requested by user and software fallback has not been forced</returns>
public bool RequestsHardwareDynamicResolution()
{
if (m_ForceSoftwareFallback)
return false;
return type == DynamicResolutionType.Hardware;
}
/// <summary>
/// Identifies whether dynamic resolution is enabled and scaling the render targets.
/// </summary>
/// <returns>True: Dynamic resolution is enabled.</returns>
public bool DynamicResolutionEnabled()
{
return m_CurrentCameraRequest && m_Enabled && m_CurrentFraction != 1.0f;
}
/// <summary>
/// Forces software fallback for dynamic resolution. Needs to be called in case Hardware dynamic resolution is requested by the user, but not supported by the platform.
/// </summary>
public void ForceSoftwareFallback()
{
m_ForceSoftwareFallback = true;
}
/// <summary>
/// Applies to the passed size the scale imposed by the dynamic resolution system.
/// Note: this function has the side effect of caching the last scale size.
/// </summary>
/// <param name="size">The starting size of the render target that will be scaled by dynamic resolution.</param>
/// <returns>The parameter size scaled by the dynamic resolution system.</returns>
public Vector2Int GetScaledSize(Vector2Int size)
{
cachedOriginalSize = size;
if (!m_Enabled || !m_CurrentCameraRequest)
{
return size;
}
Vector2Int scaledSize = ApplyScalesOnSize(size);
m_LastScaledSize = scaledSize;
return scaledSize;
}
/// <summary>
/// Applies to the passed size the scale imposed by the dynamic resolution system.
/// Note: this function is pure (has no side effects), this function does not cache the pre-scale size
/// </summary>
/// <param name="size">The size to apply the scaling</param>
/// <returns>The parameter size scaled by the dynamic resolution system.</returns>
public Vector2Int ApplyScalesOnSize(Vector2Int size)
{
Vector2 resolvedScales = GetResolvedScale();
Vector2Int scaledSize = new Vector2Int(Mathf.CeilToInt(size.x * resolvedScales.x), Mathf.CeilToInt(size.y * resolvedScales.y));
if (m_ForceSoftwareFallback || type != DynamicResolutionType.Hardware)
{
scaledSize.x += (1 & scaledSize.x);
scaledSize.y += (1 & scaledSize.y);
}
return scaledSize;
}
/// <summary>
/// Returns the scale that is currently applied by the dynamic resolution system.
/// </summary>
/// <returns>The scale that is currently applied by the dynamic resolution system.</returns>
public float GetCurrentScale()
{
return (m_Enabled && m_CurrentCameraRequest) ? m_CurrentFraction : 1.0f;
}
/// <summary>
/// Returns the latest scaled size that has been produced by GetScaledSize.
/// </summary>
/// <returns>The latest scaled size that has been produced by GetScaledSize.</returns>
public Vector2Int GetLastScaledSize()
{
return m_LastScaledSize;
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Types of dynamic resolution that can be requested. Note that if Hardware is selected, but not available on the platform, the system will fallback to Software.
/// </summary>
public enum DynamicResolutionType : byte
{
/// <summary>
/// Software dynamic resolution.
/// </summary>
Software,
/// <summary>
/// Hardware dynamic resolution.
/// </summary>
Hardware,
}
/// <summary>
/// Types of filters that can be used to upscale rendered result to native resolution.
/// </summary>
public enum DynamicResUpscaleFilter : byte
{
/// <summary>
/// Bilinear upscaling filter.
/// </summary>
Bilinear,
/// <summary>
/// Bicubic Catmull-Rom upscaling filter.
/// </summary>
CatmullRom,
/// <summary>
/// Lanczos upscaling filter.
/// </summary>
Lanczos,
/// <summary>
/// Contrast Adaptive Sharpening upscaling filter.
/// </summary>
ContrastAdaptiveSharpen,
}
/// <summary>User-facing settings for dynamic resolution.</summary>
[Serializable]
public struct GlobalDynamicResolutionSettings
{
/// <summary>Default GlobalDynamicResolutionSettings</summary>
/// <returns></returns>
public static GlobalDynamicResolutionSettings NewDefault() => new GlobalDynamicResolutionSettings()
{
maxPercentage = 100.0f,
minPercentage = 100.0f,
// It fall-backs to software when not supported, so it makes sense to have it on by default.
dynResType = DynamicResolutionType.Hardware,
upsampleFilter = DynamicResUpscaleFilter.CatmullRom,
forcedPercentage = 100.0f
};
/// <summary>Select whether the dynamic resolution is enabled or not.</summary>
public bool enabled;
/// <summary>The maximum resolution percentage that dynamic resolution can reach.</summary>
public float maxPercentage;
/// <summary>The minimum resolution percentage that dynamic resolution can reach.</summary>
public float minPercentage;
/// <summary>The type of dynamic resolution method.</summary>
public DynamicResolutionType dynResType;
/// <summary>The type of upscaling filter to use.</summary>
public DynamicResUpscaleFilter upsampleFilter;
/// <summary>Select whether dynamic resolution system will force a specific resolution percentage.</summary>
public bool forceResolution;
/// <summary>The resolution percentage forced in case forceResolution is set to true.</summary>
public float forcedPercentage;
}
}

View File

@@ -0,0 +1,13 @@
namespace UnityEngine.Rendering
{
/// <summary>
/// By implementing this interface, a render pipeline can indicate to external code it supports virtual texturing.
/// </summary>
public interface IVirtualTexturingEnabledRenderPipeline
{
/// <summary>
/// Indicates if virtual texturing is currently enabled for this render pipeline instance.
/// </summary>
bool virtualTexturingEnabled { get; }
}
}

View File

@@ -0,0 +1,163 @@
using System;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering
{
/// <summary>
/// A list that stores value on a provided memory buffer.
///
/// Usually use this to have a list on stack allocated memory.
/// </summary>
/// <typeparam name="T">The type of the data stored in the list.</typeparam>
public unsafe struct ListBuffer<T>
where T : unmanaged
{
private T* m_BufferPtr;
private int m_Capacity;
private int* m_CountPtr;
/// <summary>
/// The pointer to the memory storage.
/// </summary>
internal T* BufferPtr => m_BufferPtr;
/// <summary>
/// The number of item in the list.
/// </summary>
public int Count => *m_CountPtr;
/// <summary>
/// The maximum number of item stored in this list.
/// </summary>
public int Capacity => m_Capacity;
/// <summary>
/// Instantiate a new list.
/// </summary>
/// <param name="bufferPtr">The address in memory to store the data.</param>
/// <param name="countPtr">The address in memory to store the number of item of this list..</param>
/// <param name="capacity">The number of <typeparamref name="T"/> that can be stored in the buffer.</param>
public ListBuffer(T* bufferPtr, int* countPtr, int capacity)
{
m_BufferPtr = bufferPtr;
m_Capacity = capacity;
m_CountPtr = countPtr;
}
/// <summary>
/// Get an item from the list.
/// </summary>
/// <param name="index">The index of the item to get.</param>
/// <returns>A reference to the item.</returns>
/// <exception cref="IndexOutOfRangeException">If the index is invalid.</exception>
public ref T this[in int index]
{
get
{
if (index < 0 || index >= Count)
throw new IndexOutOfRangeException(
$"Expected a value between 0 and {Count}, but received {index}.");
return ref m_BufferPtr[index];
}
}
/// <summary>
/// Get an item from the list.
///
/// Safety: index must be inside the bounds of the list.
/// </summary>
/// <param name="index">The index of the item to get.</param>
/// <returns>A reference to the item.</returns>
public unsafe ref T GetUnchecked(in int index) => ref m_BufferPtr[index];
/// <summary>
/// Try to add a value in the list.
/// </summary>
/// <param name="value">A reference to the value to add.</param>
/// <returns>
/// <code>true</code> when the value was added,
/// <code>false</code> when the value was not added because the capacity was reached.
/// </returns>
public bool TryAdd(in T value)
{
if (Count >= m_Capacity)
return false;
m_BufferPtr[Count] = value;
++*m_CountPtr;
return true;
}
/// <summary>
/// Copy the content of this list into another buffer in memory.
///
/// Safety:
/// * The destination must have enough memory to receive the copied data.
/// </summary>
/// <param name="dstBuffer">The destination buffer of the copy operation.</param>
/// <param name="startDstIndex">The index of the first element that will be copied in the destination buffer.</param>
/// <param name="copyCount">The number of item to copy.</param>
public unsafe void CopyTo(T* dstBuffer, int startDstIndex, int copyCount)
{
UnsafeUtility.MemCpy(dstBuffer + startDstIndex, m_BufferPtr,
UnsafeUtility.SizeOf<T>() * copyCount);
}
/// <summary>
/// Try to copy the list into another list.
/// </summary>
/// <param name="other">The destination of the copy.</param>
/// <returns>
/// * <code>true</code> when the copy was performed.
/// * <code>false</code> when the copy was aborted because the destination have a capacity too small.
/// </returns>
public bool TryCopyTo(ListBuffer<T> other)
{
if (other.Count + Count >= other.m_Capacity)
return false;
UnsafeUtility.MemCpy(other.m_BufferPtr + other.Count, m_BufferPtr, UnsafeUtility.SizeOf<T>() * Count);
*other.m_CountPtr += Count;
return true;
}
/// <summary>
/// Try to copy the data from a buffer in this list.
/// </summary>
/// <param name="srcPtr">The pointer of the source memory to copy.</param>
/// <param name="count">The number of item to copy from the source buffer.</param>
/// <returns>
/// * <code>true</code> when the copy was performed.
/// * <code>false</code> when the copy was aborted because the capacity of this list is too small.
/// </returns>
public bool TryCopyFrom(T* srcPtr, int count)
{
if (count + Count > m_Capacity)
return false;
UnsafeUtility.MemCpy(m_BufferPtr + Count, srcPtr, UnsafeUtility.SizeOf<T>() * count);
*m_CountPtr += count;
return true;
}
}
/// <summary>
/// Extensions for <see cref="ListBuffer{T}"/>.
/// </summary>
public static class ListBufferExtensions
{
/// <summary>
/// Perform a quick sort on a <see cref="ListBuffer{T}"/>.
/// </summary>
/// <param name="self">The list to sort.</param>
/// <typeparam name="T">The type of the element in the list.</typeparam>
public static void QuickSort<T>(this ListBuffer<T> self)
where T : unmanaged, IComparable<T>
{
unsafe
{
CoreUnsafeUtils.QuickSort<int>(self.Count, self.BufferPtr);
}
}
}
}

View File

@@ -0,0 +1,263 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
namespace UnityEngine.Rendering
{
/// <summary>
/// Generic object pool.
/// </summary>
/// <typeparam name="T">Type of the object pool.</typeparam>
public class ObjectPool<T> where T : new()
{
readonly Stack<T> m_Stack = new Stack<T>();
readonly UnityAction<T> m_ActionOnGet;
readonly UnityAction<T> m_ActionOnRelease;
readonly bool m_CollectionCheck = true;
/// <summary>
/// Number of objects in the pool.
/// </summary>
public int countAll { get; private set; }
/// <summary>
/// Number of active objects in the pool.
/// </summary>
public int countActive { get { return countAll - countInactive; } }
/// <summary>
/// Number of inactive objects in the pool.
/// </summary>
public int countInactive { get { return m_Stack.Count; } }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="actionOnGet">Action on get.</param>
/// <param name="actionOnRelease">Action on release.</param>
/// <param name="collectionCheck">True if collection integrity should be checked.</param>
public ObjectPool(UnityAction<T> actionOnGet, UnityAction<T> actionOnRelease, bool collectionCheck = true)
{
m_ActionOnGet = actionOnGet;
m_ActionOnRelease = actionOnRelease;
m_CollectionCheck = collectionCheck;
}
/// <summary>
/// Get an object from the pool.
/// </summary>
/// <returns>A new object from the pool.</returns>
public T Get()
{
T element;
if (m_Stack.Count == 0)
{
element = new T();
countAll++;
}
else
{
element = m_Stack.Pop();
}
if (m_ActionOnGet != null)
m_ActionOnGet(element);
return element;
}
/// <summary>
/// Pooled object.
/// </summary>
public struct PooledObject : IDisposable
{
readonly T m_ToReturn;
readonly ObjectPool<T> m_Pool;
internal PooledObject(T value, ObjectPool<T> pool)
{
m_ToReturn = value;
m_Pool = pool;
}
/// <summary>
/// Disposable pattern implementation.
/// </summary>
void IDisposable.Dispose() => m_Pool.Release(m_ToReturn);
}
/// <summary>
/// Get et new PooledObject.
/// </summary>
/// <param name="v">Output new typed object.</param>
/// <returns>New PooledObject</returns>
public PooledObject Get(out T v) => new PooledObject(v = Get(), this);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="element">Object to release.</param>
public void Release(T element)
{
#if UNITY_EDITOR // keep heavy checks in editor
if (m_CollectionCheck && m_Stack.Count > 0)
{
if (m_Stack.Contains(element))
Debug.LogError("Internal error. Trying to destroy object that is already released to pool.");
}
#endif
if (m_ActionOnRelease != null)
m_ActionOnRelease(element);
m_Stack.Push(element);
}
}
/// <summary>
/// Generic pool.
/// </summary>
/// <typeparam name="T">Type of the objects in the pull.</typeparam>
public static class GenericPool<T>
where T : new()
{
// Object pool to avoid allocations.
static readonly ObjectPool<T> s_Pool = new ObjectPool<T>(null, null);
/// <summary>
/// Get a new object.
/// </summary>
/// <returns>A new object from the pool.</returns>
public static T Get() => s_Pool.Get();
/// <summary>
/// Get a new PooledObject
/// </summary>
/// <param name="value">Output typed object.</param>
/// <returns>A new PooledObject.</returns>
public static ObjectPool<T>.PooledObject Get(out T value) => s_Pool.Get(out value);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="toRelease">Object to release.</param>
public static void Release(T toRelease) => s_Pool.Release(toRelease);
}
/// <summary>
/// Generic pool without collection checks.
/// This class is an alternative for the GenericPool for object that allocate memory when they are being compared.
/// It is the case for the CullingResult class from Unity, and because of this in HDRP HDCullingResults generates garbage whenever we use ==, .Equals or ReferenceEquals.
/// This pool doesn't do any of these comparison because we don't check if the stack already contains the element before releasing it.
/// </summary>
/// <typeparam name="T">Type of the objects in the pull.</typeparam>
public static class UnsafeGenericPool<T>
where T : new()
{
// Object pool to avoid allocations.
static readonly ObjectPool<T> s_Pool = new ObjectPool<T>(null, null, false);
/// <summary>
/// Get a new object.
/// </summary>
/// <returns>A new object from the pool.</returns>
public static T Get() => s_Pool.Get();
/// <summary>
/// Get a new PooledObject
/// </summary>
/// <param name="value">Output typed object.</param>
/// <returns>A new PooledObject.</returns>
public static ObjectPool<T>.PooledObject Get(out T value) => s_Pool.Get(out value);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="toRelease">Object to release.</param>
public static void Release(T toRelease) => s_Pool.Release(toRelease);
}
/// <summary>
/// List Pool.
/// </summary>
/// <typeparam name="T">Type of the objects in the pooled lists.</typeparam>
public static class ListPool<T>
{
// Object pool to avoid allocations.
static readonly ObjectPool<List<T>> s_Pool = new ObjectPool<List<T>>(null, l => l.Clear());
/// <summary>
/// Get a new List
/// </summary>
/// <returns>A new List</returns>
public static List<T> Get() => s_Pool.Get();
/// <summary>
/// Get a new list PooledObject.
/// </summary>
/// <param name="value">Output typed List.</param>
/// <returns>A new List PooledObject.</returns>
public static ObjectPool<List<T>>.PooledObject Get(out List<T> value) => s_Pool.Get(out value);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="toRelease">List to release.</param>
public static void Release(List<T> toRelease) => s_Pool.Release(toRelease);
}
/// <summary>
/// HashSet Pool.
/// </summary>
/// <typeparam name="T">Type of the objects in the pooled hashsets.</typeparam>
public static class HashSetPool<T>
{
// Object pool to avoid allocations.
static readonly ObjectPool<HashSet<T>> s_Pool = new ObjectPool<HashSet<T>>(null, l => l.Clear());
/// <summary>
/// Get a new HashSet
/// </summary>
/// <returns>A new HashSet</returns>
public static HashSet<T> Get() => s_Pool.Get();
/// <summary>
/// Get a new list PooledObject.
/// </summary>
/// <param name="value">Output typed HashSet.</param>
/// <returns>A new HashSet PooledObject.</returns>
public static ObjectPool<HashSet<T>>.PooledObject Get(out HashSet<T> value) => s_Pool.Get(out value);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="toRelease">hashSet to release.</param>
public static void Release(HashSet<T> toRelease) => s_Pool.Release(toRelease);
}
/// <summary>
/// Dictionary Pool.
/// </summary>
/// <typeparam name="TKey">Key type.</typeparam>
/// <typeparam name="TValue">Value type.</typeparam>
public static class DictionaryPool<TKey, TValue>
{
// Object pool to avoid allocations.
static readonly ObjectPool<Dictionary<TKey, TValue>> s_Pool
= new ObjectPool<Dictionary<TKey, TValue>>(null, l => l.Clear());
/// <summary>
/// Get a new Dictionary
/// </summary>
/// <returns>A new Dictionary</returns>
public static Dictionary<TKey, TValue> Get() => s_Pool.Get();
/// <summary>
/// Get a new dictionary PooledObject.
/// </summary>
/// <param name="value">Output typed Dictionary.</param>
/// <returns>A new Dictionary PooledObject.</returns>
public static ObjectPool<Dictionary<TKey, TValue>>.PooledObject Get(out Dictionary<TKey, TValue> value)
=> s_Pool.Get(out value);
/// <summary>
/// Release an object to the pool.
/// </summary>
/// <param name="toRelease">Dictionary to release.</param>
public static void Release(Dictionary<TKey, TValue> toRelease) => s_Pool.Release(toRelease);
}
}

View File

@@ -0,0 +1,252 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// On List Changed Event Args.
/// </summary>
/// <typeparam name="T">List type.</typeparam>
public sealed class ListChangedEventArgs<T> : EventArgs
{
/// <summary>
/// Index
/// </summary>
public readonly int index;
/// <summary>
/// Item
/// </summary>
public readonly T item;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
public ListChangedEventArgs(int index, T item)
{
this.index = index;
this.item = item;
}
}
/// <summary>
/// List changed event handler.
/// </summary>
/// <typeparam name="T">List type.</typeparam>
/// <param name="sender">Sender.</param>
/// <param name="e">List changed even arguments.</param>
public delegate void ListChangedEventHandler<T>(ObservableList<T> sender, ListChangedEventArgs<T> e);
/// <summary>
/// Observable list.
/// </summary>
/// <typeparam name="T">Type of the list.</typeparam>
public class ObservableList<T> : IList<T>
{
IList<T> m_List;
/// <summary>
/// Added item event.
/// </summary>
public event ListChangedEventHandler<T> ItemAdded;
/// <summary>
/// Removed item event.
/// </summary>
public event ListChangedEventHandler<T> ItemRemoved;
/// <summary>
/// Accessor.
/// </summary>
/// <param name="index">Item index.</param>
/// <returns>The item at the provided index.</returns>
public T this[int index]
{
get { return m_List[index]; }
set
{
OnEvent(ItemRemoved, index, m_List[index]);
m_List[index] = value;
OnEvent(ItemAdded, index, value);
}
}
/// <summary>
/// Number of elements in the list.
/// </summary>
public int Count
{
get { return m_List.Count; }
}
/// <summary>
/// Is the list read only?
/// </summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Default Constructor.
/// </summary>
public ObservableList()
: this(0) {}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="capacity">Allocation size.</param>
public ObservableList(int capacity)
{
m_List = new List<T>(capacity);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="collection">Input list.</param>
public ObservableList(IEnumerable<T> collection)
{
m_List = new List<T>(collection);
}
void OnEvent(ListChangedEventHandler<T> e, int index, T item)
{
if (e != null)
e(this, new ListChangedEventArgs<T>(index, item));
}
/// <summary>
/// Check if an element is present in the list.
/// </summary>
/// <param name="item">Item to test against.</param>
/// <returns>True if the item is in the list.</returns>
public bool Contains(T item)
{
return m_List.Contains(item);
}
/// <summary>
/// Get the index of an item.
/// </summary>
/// <param name="item">The object to locate in the list.</param>
/// <returns>The index of the item in the list if it exists, -1 otherwise.</returns>
public int IndexOf(T item)
{
return m_List.IndexOf(item);
}
/// <summary>
/// Add an item to the list.
/// </summary>
/// <param name="item">Item to add to the list.</param>
public void Add(T item)
{
m_List.Add(item);
OnEvent(ItemAdded, m_List.IndexOf(item), item);
}
/// <summary>
/// Add multiple objects to the list.
/// </summary>
/// <param name="items">Items to add to the list.</param>
public void Add(params T[] items)
{
foreach (var i in items)
Add(i);
}
/// <summary>
/// Insert an item in the list.
/// </summary>
/// <param name="index">Index at which to insert the new item.</param>
/// <param name="item">Item to insert in the list.</param>
public void Insert(int index, T item)
{
m_List.Insert(index, item);
OnEvent(ItemAdded, index, item);
}
/// <summary>
/// Remove an item from the list.
/// </summary>
/// <param name="item">Item to remove from the list.</param>
/// <returns>True if the item was successfuly removed. False otherise.</returns>
public bool Remove(T item)
{
int index = m_List.IndexOf(item);
bool ret = m_List.Remove(item);
if (ret)
OnEvent(ItemRemoved, index, item);
return ret;
}
/// <summary>
/// Remove multiple items from the list.
/// </summary>
/// <param name="items">Items to remove from the list.</param>
/// <returns>The number of removed items.</returns>
public int Remove(params T[] items)
{
if (items == null)
return 0;
int count = 0;
foreach (var i in items)
count += Remove(i) ? 1 : 0;
return count;
}
/// <summary>
/// Remove an item at a specific index.
/// </summary>
/// <param name="index">Index of the item to remove.</param>
public void RemoveAt(int index)
{
var item = m_List[index];
m_List.RemoveAt(index);
OnEvent(ItemRemoved, index, item);
}
/// <summary>
/// Clear the list.
/// </summary>
public void Clear()
{
for (int i = 0; i < Count; i++)
RemoveAt(i);
}
/// <summary>
/// Copy items in the list to an array.
/// </summary>
/// <param name="array">Destination array.</param>
/// <param name="arrayIndex">Starting index.</param>
public void CopyTo(T[] array, int arrayIndex)
{
m_List.CopyTo(array, arrayIndex);
}
/// <summary>
/// Get enumerator.
/// </summary>
/// <returns>The list enumerator.</returns>
public IEnumerator<T> GetEnumerator()
{
return m_List.GetEnumerator();
}
/// <summary>
/// Get enumerator.
/// </summary>
/// <returns>The list enumerator.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
//
// Unity can't serialize Dictionary so here's a custom wrapper that does. Note that you have to
// extend it before it can be serialized as Unity won't serialized generic-based types either.
//
// Example:
// public sealed class MyDictionary : SerializedDictionary<KeyType, ValueType> {}
//
/// <summary>
/// Serialized Dictionary
/// </summary>
/// <typeparam name="K">Key Type</typeparam>
/// <typeparam name="V">Value Type</typeparam>
[Serializable]
public class SerializedDictionary<K, V> : Dictionary<K, V>, ISerializationCallbackReceiver
{
[SerializeField]
List<K> m_Keys = new List<K>();
[SerializeField]
List<V> m_Values = new List<V>();
/// <summary>
/// OnBeforeSerialize implementation.
/// </summary>
public void OnBeforeSerialize()
{
m_Keys.Clear();
m_Values.Clear();
foreach (var kvp in this)
{
m_Keys.Add(kvp.Key);
m_Values.Add(kvp.Value);
}
}
/// <summary>
/// OnAfterDeserialize implementation.
/// </summary>
public void OnAfterDeserialize()
{
for (int i = 0; i < m_Keys.Count; i++)
Add(m_Keys[i], m_Values[i]);
m_Keys.Clear();
m_Values.Clear();
}
}
}

View File

@@ -0,0 +1,207 @@
using System;
using UnityEditor;
#if ENABLE_VR && ENABLE_VR_MODULE
using UnityEngine.XR;
#endif
namespace UnityEngine.Rendering
{
/// <summary>
/// XRGraphics insulates SRP from API changes across platforms, Editor versions, and as XR transitions into XR SDK
/// </summary>
[Serializable]
public class XRGraphics
{
/// <summary>
/// Stereo Rendering Modes.
/// </summary>
public enum StereoRenderingMode
{
/// <summary>Multi Pass.</summary>
MultiPass = 0,
/// <summary>Single Pass.</summary>
SinglePass,
/// <summary>Single Pass Instanced.</summary>
SinglePassInstanced,
/// <summary>Single Pass Multi View.</summary>
SinglePassMultiView
};
/// <summary>
/// Eye texture resolution scale.
/// </summary>
public static float eyeTextureResolutionScale
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.eyeTextureResolutionScale;
#endif
return 1.0f;
}
set
{
#if ENABLE_VR && ENABLE_VR_MODULE
XRSettings.eyeTextureResolutionScale = value;
#endif
}
}
/// <summary>
/// Render viewport scale.
/// </summary>
public static float renderViewportScale
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.renderViewportScale;
#endif
return 1.0f;
}
}
/// <summary>
/// Try enable.
/// </summary>
#if UNITY_EDITOR
// TryEnable gets updated before "play" is pressed- we use this for updating GUI only.
public static bool tryEnable
{
get
{
#if UNITY_2020_1_OR_NEWER
return false;
#else
return UnityEditorInternal.VR.VREditor.GetVREnabledOnTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
#endif
}
}
#endif
/// <summary>
/// SRP should use this to safely determine whether XR is enabled at runtime.
/// </summary>
public static bool enabled
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
return XRSettings.enabled;
#else
return false;
#endif
}
}
/// <summary>
/// Returns true if the XR device is active.
/// </summary>
public static bool isDeviceActive
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.isDeviceActive;
#endif
return false;
}
}
/// <summary>
/// Name of the loaded XR device.
/// </summary>
public static string loadedDeviceName
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.loadedDeviceName;
#endif
return "No XR device loaded";
}
}
/// <summary>
/// List of supported XR devices.
/// </summary>
public static string[] supportedDevices
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.supportedDevices;
#endif
return new string[1];
}
}
/// <summary>
/// Stereo rendering mode.
/// </summary>
public static StereoRenderingMode stereoRenderingMode
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return (StereoRenderingMode)XRSettings.stereoRenderingMode;
#endif
return StereoRenderingMode.SinglePass;
}
}
/// <summary>
/// Eye texture descriptor.
/// </summary>
public static RenderTextureDescriptor eyeTextureDesc
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.eyeTextureDesc;
#endif
return new RenderTextureDescriptor(0, 0);
}
}
/// <summary>
/// Eye texture width.
/// </summary>
public static int eyeTextureWidth
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.eyeTextureWidth;
#endif
return 0;
}
}
/// <summary>
/// Eye texture height.
/// </summary>
public static int eyeTextureHeight
{
get
{
#if ENABLE_VR && ENABLE_VR_MODULE
if (enabled)
return XRSettings.eyeTextureHeight;
#endif
return 0;
}
}
}
}