This commit is contained in:
2024-09-20 20:30:10 +02:00
commit 4fabf1a6fd
29169 changed files with 1706941 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,12 @@
fileFormatVersion: 2
guid: bc85748f0cef607499e03b46e3846ebd
timeCreated: 1497364325
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Render Textures clear flag.
/// This is an legacy alias for RTClearFlags.
/// </summary>
[Flags]
public enum ClearFlag
{
/// <summary>Don't clear.</summary>
None = RTClearFlags.None,
/// <summary>Clear the color buffer.</summary>
Color = RTClearFlags.Color,
/// <summary>Clear the depth buffer.</summary>
Depth = RTClearFlags.Depth,
/// <summary>Clear the stencil buffer.</summary>
Stencil = RTClearFlags.Stencil,
/// <summary>Clear the depth and stencil buffers.</summary>
DepthStencil = Depth | Stencil,
/// <summary>Clear the color and stencil buffers.</summary>
ColorStencil = Color | Stencil,
/// <summary>Clear both color, depth and stencil buffers.</summary>
All = Color | Depth | Stencil
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df81bb6ebd9587b4a87f9974080eebf1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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,11 @@
fileFormatVersion: 2
guid: f78c611171f4b4946b5cf8394378931f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,391 @@
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 via a command 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>
/// <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 globally.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <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>(in CBType data, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(data);
cb.SetGlobal(shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer and bind it to a compute shader via a command 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>
/// <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 compute shader.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <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>(in CBType data, ComputeShader cs, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(data);
cb.Set(cs, shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer and bind it to a material via a command 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>
/// <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 and bind it to a material.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <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>(in CBType data, Material mat, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(data);
cb.Set(mat, shaderId);
}
/// <summary>
/// Update the GPU data of the constant buffer via a command 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>
/// Update the GPU data of the constant buffer.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="data">Input data of the constant buffer.</param>
public static void UpdateData<CBType>(in CBType data) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.UpdateData(data);
}
/// <summary>
/// Bind the constant buffer globally via a command 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="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 globally.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public static void SetGlobal<CBType>(int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.SetGlobal(shaderId);
}
/// <summary>
/// Bind the constant buffer to a compute shader via a command 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="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 compute shader.
/// </summary>
/// <typeparam name="CBType">The type of structure representing the constant buffer data.</typeparam>
/// <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>(ComputeShader cs, int shaderId) where CBType : struct
{
var cb = ConstantBufferSingleton<CBType>.instance;
cb.Set(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 via a command 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>
/// Update the GPU data of the constant buffer.
/// </summary>
/// <param name="data">Input data of the constant buffer.</param>
public void UpdateData(in CBType data)
{
m_Data[0] = data;
m_GPUConstantBuffer.SetData(m_Data);
}
/// <summary>
/// Bind the constant buffer globally via a command buffer.
/// </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 globally.
/// </summary>
/// <param name="shaderId">Shader porperty id to bind the constant buffer to.</param>
public void SetGlobal(int shaderId)
{
m_GlobalBindings.Add(shaderId);
Shader.SetGlobalConstantBuffer(shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride);
}
/// <summary>
/// Bind the constant buffer to a compute shader via a command buffer.
/// </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 compute shader.
/// </summary>
/// <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(ComputeShader cs, int shaderId)
{
cs.SetConstantBuffer(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 via a command buffer.
/// </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>
/// Update the GPU data of the constant buffer and bind it globally.
/// </summary>
/// <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(in CBType data, int shaderId)
{
UpdateData(data);
SetGlobal(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,11 @@
fileFormatVersion: 2
guid: 61649e120aa78c04e8b44d1c5af17d35
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Attribute used to customize UI display.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false)]
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,11 @@
fileFormatVersion: 2
guid: 07346c0b2cba0214f8f27c46ec2dd613
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
namespace UnityEngine.Rendering
{
internal enum CoreProfileId
{
BlitTextureInPotAtlas,
APVCellStreamingUpdate,
APVScenarioBlendingUpdate,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da3d290bbfba048588cdeed618e4ad64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,714 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
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]);
}
private static void CalculateRadixParams(int radixBits, out int bitStates)
{
if (radixBits != 2 && radixBits != 4 && radixBits != 8)
throw new Exception("Radix bits must be 2, 4 or 8 for uint radix sort.");
bitStates = 1 << radixBits;
}
private static int CalculateRadixSupportSize(int bitStates, int arrayLength)
{
return bitStates * 3 + arrayLength;
}
private static unsafe void CalculateRadixSortSupportArrays(
int bitStates, int arrayLength, uint* supportArray,
out uint* bucketIndices, out uint* bucketSizes, out uint* bucketPrefix, out uint* arrayOutput)
{
bucketIndices = supportArray;
bucketSizes = bucketIndices + bitStates;
bucketPrefix = bucketSizes + bitStates;
arrayOutput = bucketPrefix + bitStates;
}
private static unsafe void MergeSort(uint* array, uint* support, int length)
{
for (int k = 1; k < length; k *= 2)
{
for (int left = 0; left + k < length; left += k * 2)
{
int right = left + k;
int rightend = right + k;
if (rightend > length)
rightend = length;
int m = left;
int i = left;
int j = right;
while (i < right && j < rightend)
{
if (array[i] <= array[j])
{
support[m] = array[i++];
}
else
{
support[m] = array[j++];
}
m++;
}
while (i < right)
{
support[m] = array[i++];
m++;
}
while (j < rightend)
{
support[m] = array[j++];
m++;
}
for (m = left; m < rightend; m++)
{
array[m] = support[m];
}
}
}
}
/// <summary>
/// Merge sort - non recursive
/// </summary>
/// <param name="arr">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
/// <param name="supportArray">Secondary array reference, used to store intermediate merge results.</param>
public static unsafe void MergeSort(uint[] arr, int sortSize, ref uint[] supportArray)
{
sortSize = Math.Min(sortSize, arr.Length);
if (arr == null || sortSize == 0)
return;
if (supportArray == null || supportArray.Length < sortSize)
supportArray = new uint[sortSize];
fixed (uint* arrPtr = arr)
fixed (uint* supportPtr = supportArray)
CoreUnsafeUtils.MergeSort(arrPtr, supportPtr, sortSize);
}
/// <summary>
/// Merge sort - non recursive
/// </summary>
/// <param name="arr">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
/// <param name="supportArray">Secondary array reference, used to store intermediate merge results.</param>
public static unsafe void MergeSort(NativeArray<uint> arr, int sortSize, ref NativeArray<uint> supportArray)
{
sortSize = Math.Min(sortSize, arr.Length);
if (!arr.IsCreated || sortSize == 0)
return;
if (!supportArray.IsCreated || supportArray.Length < sortSize)
supportArray.ResizeArray(arr.Length);
CoreUnsafeUtils.MergeSort((uint*)arr.GetUnsafePtr(), (uint*)supportArray.GetUnsafePtr(), sortSize);
}
private static unsafe void InsertionSort(uint* arr, int length)
{
for (int i = 0; i < length; ++i)
{
for (int j = i; j >= 1; --j)
{
if (arr[j] >= arr[j - 1])
break;
var tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
}
}
}
/// <summary>
/// Insertion sort
/// </summary>
/// <param name="arr">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
public static unsafe void InsertionSort(uint[] arr, int sortSize)
{
sortSize = Math.Min(arr.Length, sortSize);
if (arr == null || sortSize == 0)
return;
fixed (uint* ptr = arr)
CoreUnsafeUtils.InsertionSort(ptr, sortSize);
}
/// <summary>
/// Insertion sort
/// </summary>
/// <param name="arr">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
public static unsafe void InsertionSort(NativeArray<uint> arr, int sortSize)
{
sortSize = Math.Min(arr.Length, sortSize);
if (!arr.IsCreated || sortSize == 0)
return;
CoreUnsafeUtils.InsertionSort((uint*)arr.GetUnsafePtr(), sortSize);
}
private static unsafe void RadixSort(uint* array, uint* support, int radixBits, int bitStates, int length)
{
uint mask = (uint)(bitStates - 1);
CalculateRadixSortSupportArrays(bitStates, length, support, out uint* bucketIndices, out uint* bucketSizes, out uint* bucketPrefix, out uint* arrayOutput);
int buckets = (sizeof(uint) * 8) / radixBits;
uint* targetBuffer = arrayOutput;
uint* inputBuffer = array;
for (int b = 0; b < buckets; ++b)
{
int shift = b * radixBits;
for (int s = 0; s < 3 * bitStates; ++s)
bucketIndices[s] = 0;//bucketSizes and bucketPrefix get zeroed, since we walk 3x the bit states
for (int i = 0; i < length; ++i)
bucketSizes[((inputBuffer[i] >> shift) & mask)]++;
for (int s = 1; s < bitStates; ++s)
bucketPrefix[s] = bucketPrefix[s - 1] + bucketSizes[s - 1];
for (int i = 0; i < length; ++i)
{
uint val = inputBuffer[i];
uint bucket = (val >> shift) & mask;
targetBuffer[bucketPrefix[bucket] + bucketIndices[bucket]++] = val;
}
uint* tmp = inputBuffer;
inputBuffer = targetBuffer;
targetBuffer = tmp;
}
}
/// <summary>
/// Radix sort or bucket sort, stable and non in place.
/// </summary>
/// <param name="arr">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
/// <param name="supportArray">Array of uints that is used for support data. The algorithm will automatically allocate it if necessary.</param>
/// <param name="radixBits">Number of bits to use for each bucket. Can only be 8, 4 or 2.</param>
public static unsafe void RadixSort(uint[] arr, int sortSize, ref uint[] supportArray, int radixBits = 8)
{
sortSize = Math.Min(sortSize, arr.Length);
CalculateRadixParams(radixBits, out int bitStates);
if (arr == null || sortSize == 0)
return;
int supportSize = CalculateRadixSupportSize(bitStates, sortSize);
if (supportArray == null || supportArray.Length < supportSize)
supportArray = new uint[supportSize];
fixed (uint* ptr = arr)
fixed (uint* supportArrayPtr = supportArray)
CoreUnsafeUtils.RadixSort(ptr, supportArrayPtr, radixBits, bitStates, sortSize);
}
/// <summary>
/// Radix sort or bucket sort, stable and non in place.
/// </summary>
/// <param name="array">Array to sort.</param>
/// <param name="sortSize">Size of the array to sort. If greater than array capacity, it will get clamped.</param>
/// <param name="supportArray">Array of uints that is used for support data. The algorithm will automatically allocate it if necessary.</param>
/// <param name="radixBits">Number of bits to use for each bucket. Can only be 8, 4 or 2.</param>
public static unsafe void RadixSort(NativeArray<uint> array, int sortSize, ref NativeArray<uint> supportArray, int radixBits = 8)
{
sortSize = Math.Min(sortSize, array.Length);
CalculateRadixParams(radixBits, out int bitStates);
if (!array.IsCreated || sortSize == 0)
return;
int supportSize = CalculateRadixSupportSize(bitStates, sortSize);
if (!supportArray.IsCreated || supportArray.Length < supportSize)
supportArray.ResizeArray((int)supportSize);
CoreUnsafeUtils.RadixSort((uint*)array.GetUnsafePtr(), (uint*)supportArray.GetUnsafePtr(), radixBits, bitStates, sortSize);
}
/// <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,11 @@
fileFormatVersion: 2
guid: 69a7aaed92ae1b0469d9b82fba45bcec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,591 @@
using System;
using System.Diagnostics;
namespace UnityEngine.Rendering
{
/// <summary>
/// Generic growable array.
/// </summary>
/// <typeparam name="T">Type of the array.</typeparam>
[DebuggerDisplay("Size = {size} Capacity = {capacity}")]
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; } }
#if DEVELOPMENT_BUILD || UNITY_EDITOR
/// <summary>
/// This keeps track of structural modifications to this array and allows us to raise exceptions when modifying during enumeration
/// </summary>
internal int version { get; private set; }
#endif
/// <summary>
/// Constructor.
/// Defaults to a size of 32 elements.
/// </summary>
public DynamicArray()
{
m_Array = new T[32];
size = 0;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
version = 0;
#endif
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="size">Number of elements.</param>
public DynamicArray(int size)
{
m_Array = new T[size];
this.size = size;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
version = 0;
#endif
}
/// <summary>
/// Clear the array of all elements.
/// </summary>
public void Clear()
{
size = 0;
}
/// <summary>
/// Determines whether the DynamicArray contains a specific value.
/// </summary>
/// <param name="item">The object to locate in the DynamicArray.</param>
/// <returns>true if item is found in the DynamicArray; otherwise, false.</returns>
public bool Contains(T item)
{
return IndexOf(item) != -1;
}
/// <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++;
BumpVersion();
return index;
}
/// <summary>
/// Adds the elements of the specified collection to the end of the DynamicArray.
/// </summary>
/// <param name="array">The array whose elements should be added to the end of the DynamicArray. The array itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>
public void AddRange(DynamicArray<T> array)
{
Reserve(size + array.size, true);
for (int i = 0; i < array.size; ++i)
m_Array[size++] = array[i];
BumpVersion();
}
/// <summary>
/// Removes the first occurrence of a specific object from the DynamicArray.
/// </summary>
/// <param name="item">The object to remove from the DynamicArray. The value can be null for reference types.</param>
/// <returns>true if item is successfully removed; otherwise, false. This method also returns false if item was not found in the DynamicArray.</returns>
public bool Remove(T item)
{
int index = IndexOf(item);
if (index != -1)
{
RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// Removes the element at the specified index of the DynamicArray.
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= size)
throw new IndexOutOfRangeException();
if (index != size - 1)
Array.Copy(m_Array, index + 1, m_Array, index, size - index - 1);
size--;
BumpVersion();
}
/// <summary>
/// Removes a range of elements from the DynamicArray.
/// </summary>
/// <param name="index">The zero-based starting index of the range of elements to remove.</param>
/// <param name="count">The number of elements to remove.</param>
public void RemoveRange(int index, int count)
{
if (count == 0)
return;
if (index < 0 || index >= size || count < 0 || index + count > size)
throw new ArgumentOutOfRangeException();
Array.Copy(m_Array, index + count, m_Array, index, size - index - count);
size -= count;
BumpVersion();
}
/// <summary>
/// Searches for an element that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that starts at the specified index and contains the specified number of elements.
/// </summary>
/// <param name="startIndex">The zero-based starting index of the search.</param>
/// <param name="count">The number of elements in the section to search.</param>
/// <param name="match">The Predicate delegate that defines the conditions of the element to search for.</param>
/// <returns>The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, -1.</returns>
public int FindIndex(int startIndex, int count, Predicate<T> match)
{
for (int i = startIndex; i < size; ++i)
{
if (match(m_Array[i]))
{
return i;
}
}
return -1;
}
/// <summary>
/// Searches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that starts at the specified index and contains the specified number of elements.
/// </summary>
/// <param name="item">The object to locate in the DynamicArray. The value can be null for reference types.</param>
/// <param name="index">The zero-based starting index of the search. 0 (zero) is valid in an empty list.</param>
/// <param name="count">The number of elements in the section to search.</param>
/// <returns></returns>
public int IndexOf(T item, int index, int count)
{
for (int i = index; i < size && count > 0; ++i, --count)
{
if (m_Array[i].Equals(item))
{
return i;
}
}
return -1;
}
/// <summary>
/// Searches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DynamicArray that extends from the specified index to the last element.
/// </summary>
/// <param name="item">The object to locate in the DynamicArray. The value can be null for reference types.</param>
/// <param name="index">The zero-based starting index of the search. 0 (zero) is valid in an empty list.</param>
/// <returns>The zero-based index of the first occurrence of item within the range of elements in the DynamicArray that extends from index to the last element, if found; otherwise, -1.</returns>
public int IndexOf(T item, int index)
{
for (int i = index; i < size; ++i)
{
if (m_Array[i].Equals(item))
{
return i;
}
}
return -1;
}
/// <summary>
/// Searches for the specified object and returns the zero-based index of the first occurrence within the entire DynamicArray.
/// </summary>
/// <param name="item">The object to locate in the DynamicArray. The value can be null for reference types.</param>
/// <returns>he zero-based index of the first occurrence of item within the entire DynamicArray, if found; otherwise, -1.</returns>
public int IndexOf(T item)
{
return IndexOf(item, 0);
}
/// <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)
{
Reserve(newSize, keepContent);
size = newSize;
BumpVersion();
}
/// <summary>
/// Sets the total number of elements the internal data structure can hold without resizing.
/// </summary>
/// <param name="newCapacity">New capacity 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 Reserve(int newCapacity, bool keepContent = false)
{
if (newCapacity > m_Array.Length)
{
if (keepContent)
{
var newArray = new T[newCapacity];
Array.Copy(m_Array, newArray, m_Array.Length);
m_Array = newArray;
}
else
{
m_Array = new T[newCapacity];
}
}
}
/// <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];
}
}
/// <summary>
/// Implicit conversion to regular array.
/// </summary>
/// <param name="array">Input DynamicArray.</param>
/// <returns>The internal array.</returns>
public static implicit operator T[](DynamicArray<T> array) => array.m_Array;
/// <summary>
/// IEnumerator-like struct used to loop over this entire array. See the IEnumerator docs for more info:
/// <see href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerator" langword="IEnumerator" />
/// </summary>
/// <remarks>
/// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as IEnumerable but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <seealso cref="RangeIterator"/>
public struct Iterator
{
private readonly DynamicArray<T> owner;
private int index;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private int localVersion;
#endif
/// <summary>
/// Creates an iterator to iterate over an array.
/// </summary>
/// <param name="setOwner">The array to iterate over.</param>
/// <exception cref="ArgumentNullException">Thrown if the array is null.</exception>
public Iterator(DynamicArray<T> setOwner)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (setOwner == null)
throw new ArgumentNullException();
#endif
owner = setOwner;
index = -1;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
localVersion = owner.version;
#endif
}
/// <summary>
/// Gets the element in the DynamicArray at the current position of the iterator.
/// </summary>
public ref T Current
{
get
{
return ref owner[index];
}
}
/// <summary>
/// Advances the iterator to the next element of the DynamicArray.
/// </summary>
/// <returns>Returns <c>true</c> if the iterator has successfully advanced to the next element; <c>false</c> if the iterator has passed the end of the DynamicArray.</returns>
/// <exception cref="InvalidOperationException">An operation changed the DynamicArray after the creation of this iterator.</exception>
public bool MoveNext()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (owner.version != localVersion)
{
throw new InvalidOperationException("DynamicArray was modified during enumeration");
}
#endif
index++;
return index < owner.size;
}
/// <summary>
/// Sets the iterator to its initial position, which is before the first element in the DynamicArray.
/// </summary>
public void Reset()
{
index = -1;
}
}
/// <summary>
/// Returns an enumerator that iterates through of this array.
/// See the IEnumerable docs for more info: <see href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerable" langword="IEnumarable" />
/// </summary>
/// <remarks>
/// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as IEnumerable but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <returns>Iterator pointing before the first element in the array.</returns>
public Iterator GetEnumerator()
{
return new Iterator(this);
}
/// <summary>
/// IEnumerable-like struct used to iterate through a subsection of this array.
/// See the IEnumerable docs for more info: <see href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerable" langword="IEnumarable" />
/// </summary>
/// <remarks>
/// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as IEnumerable but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <seealso cref="SubRange"/>
public struct RangeEnumerable
{
/// <summary>
/// IEnumerator-like struct used to iterate through a subsection of this array.
/// See the IEnumerator docs for more info: <see href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerator" langword="IEnumerator" />
/// </summary>
/// <remarks>
/// This struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as <c>IEnumarable</c> but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <seealso cref="SubRange"/>
public struct RangeIterator
{
private readonly DynamicArray<T> owner;
private int index;
private int first;
private int last;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private int localVersion;
#endif
/// <summary>
/// Create an iterator to iterate over the given range in the array.
/// </summary>
/// <param name="setOwner">The array to iterate over.</param>
/// <param name="first">The index of the first item in the array.</param>
/// <param name="numItems">The number of array members to iterate through.</param>
/// <exception cref="ArgumentNullException">Thrown if the array is null.</exception>
public RangeIterator(DynamicArray<T> setOwner, int first, int numItems)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (setOwner == null)
throw new ArgumentNullException();
if (first < 0 || first > setOwner.size || (first + numItems) > setOwner.size)
throw new IndexOutOfRangeException();
#endif
owner = setOwner;
this.first = first;
index = first-1;
last = first + numItems;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
localVersion = owner.version;
#endif
}
/// <summary>
/// Gets the element in the DynamicArray at the current position of the iterator.
/// </summary>
public ref T Current
{
get
{
return ref owner[index];
}
}
/// <summary>
/// Advances the iterator to the next element of the DynamicArray.
/// </summary>
/// <returns>Returs <c>true</c> if the iterator successfully advanced to the next element; returns <c>false</c> if the iterator has passed the end of the range.</returns>
/// <exception cref="InvalidOperationException">The DynamicArray was modified after the iterator was created.</exception>
public bool MoveNext()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (owner.version != localVersion)
{
throw new InvalidOperationException("DynamicArray was modified during enumeration");
}
#endif
index++;
return index < last;
}
/// <summary>
/// Sets the iterator to its initial position, which is before the first element in the range.
/// </summary>
public void Reset()
{
index = first-1;
}
}
/// <summary>
/// The iterator associated with this Enumerable.
/// </summary>
public RangeIterator iterator;
/// <summary>
/// Returns an enumerator that iterates through this array.
/// </summary>
/// <remarks>
/// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as IEnumerable but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <returns>Iterator pointing before the first element in the range.</returns>
public RangeIterator GetEnumerator()
{
return iterator;
}
}
/// <summary>
/// Returns an IEnumeralbe-Like object that iterates through a subsection of this array.
/// </summary>
/// <remarks>
/// The returned struct intentionally does not explicitly implement the IEnumarable/IEnumerator interfaces it just follows
/// the same function signatures. This means the duck typing used by <c>foreach</c> on the compiler level will
/// pick it up as IEnumerable but at the same time avoids generating Garbage.
/// For more info, see the C# language specification of the <c>foreach</c> statement.
/// </remarks>
/// <param name="first">The index of the first item</param>
/// <param name="numItems">The number of items to iterate</param>
/// <returns><c>RangeEnumerable</c> that can be used to enumerate the given range.</returns>
/// <seealso cref="RangeIterator"/>
public RangeEnumerable SubRange(int first, int numItems)
{
RangeEnumerable r = new RangeEnumerable { iterator = new RangeEnumerable.RangeIterator(this, first, numItems) };
return r;
}
internal void BumpVersion()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
version++;
#endif
}
}
/// <summary>
/// Extension class for DynamicArray
/// </summary>
public static class DynamicArrayExtensions
{
static int Partition<T>(T[] data, int left, int right) where T : IComparable<T>, new()
{
var pivot = data[left];
--left;
++right;
while (true)
{
var c = 0;
var lvalue = default(T);
do
{
++left;
lvalue = data[left];
c = lvalue.CompareTo(pivot);
}
while (c < 0);
var rvalue = default(T);
do
{
--right;
rvalue = data[right];
c = rvalue.CompareTo(pivot);
}
while (c > 0);
if (left < right)
{
data[right] = lvalue;
data[left] = rvalue;
}
else
{
return right;
}
}
}
static void QuickSort<T>(T[] data, int left, int right) where T : IComparable<T>, new()
{
if (left < right)
{
int pivot = Partition(data, left, right);
if (pivot >= 1)
QuickSort(data, left, pivot);
if (pivot + 1 < right)
QuickSort(data, pivot + 1, right);
}
}
/// <summary>
/// Perform a quick sort on the DynamicArray
/// </summary>
/// <typeparam name="T">Type of the array.</typeparam>
/// <param name="array">Array on which to perform the quick sort.</param>
public static void QuickSort<T>(this DynamicArray<T> array) where T : IComparable<T>, new()
{
QuickSort<T>(array, 0, array.size - 1);
array.BumpVersion();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4cce54895670bb4894d26f27e71ba26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,614 @@
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 source slots for dynamic resolution scaler. Defines registers were the scalers assigned are stored. By default the User one is always used
/// </summary>
public enum DynamicResScalerSlot
{
/// <summary> Scaler slot set by the function SetDynamicResScaler</summary>
User,
/// <summary> Scaler slot set by the function SetSystemDynamicResScaler</summary>
System,
/// <summary> total number of scaler slots </summary>
Count
}
/// <summary>
/// The class responsible to handle dynamic resolution.
/// </summary>
public class DynamicResolutionHandler
{
private bool m_Enabled;
private bool m_UseMipBias;
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 bool m_RunUpscalerFilterOnFullResolution;
private float m_PrevHWScaleWidth;
private float m_PrevHWScaleHeight;
private Vector2Int m_LastScaledSize;
private void Reset()
{
m_Enabled = false;
m_UseMipBias = 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_RunUpscalerFilterOnFullResolution = false;
m_PrevHWScaleWidth = 1.0f;
m_PrevHWScaleHeight = 1.0f;
m_LastScaledSize = new Vector2Int(0, 0);
filter = DynamicResUpscaleFilter.CatmullRom;
}
private struct ScalerContainer
{
public DynamicResScalePolicyType type;
public PerformDynamicRes method;
}
private static DynamicResScalerSlot s_ActiveScalerSlot = DynamicResScalerSlot.User;
private static ScalerContainer[] s_ScalerContainers = new ScalerContainer[(int)DynamicResScalerSlot.Count]
{
new ScalerContainer() { type = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor, method = DefaultDynamicResMethod },
new ScalerContainer() { type = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor, method = 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; private set; }
// Used to detect the filters set via user API
static Dictionary<int, DynamicResUpscaleFilter> s_CameraUpscaleFilters = new Dictionary<int, DynamicResUpscaleFilter>();
/// <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; }
/// <summary>
/// By default, dynamic resolution scaling is turned off automatically when the source matches the final viewport (100% scale).
/// That is, DynamicResolutionEnabled and SoftwareDynamicResIsEnabled will return false if the scale is 100%.
/// For certain upscalers, we dont want this behavior since they could possibly include anti aliasing and other quality improving post processes.
/// Setting this to true will eliminate this behavior.
/// Note: when the EdgeAdaptiveScalingUpres (FSR 1.0) filter is set, this will cause this parameter to always be true.
/// </summary>
public bool runUpscalerFilterOnFullResolution
{
set { m_RunUpscalerFilterOnFullResolution = value; }
get { return m_RunUpscalerFilterOnFullResolution || filter == DynamicResUpscaleFilter.EdgeAdaptiveScalingUpres; }
}
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;
float currentFraction = s_GlobalHwUpresActive ? s_GlobalHwFraction : 1.0f;
ScalableBufferManager.ResizeBuffers(currentFraction, currentFraction);
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);
s_CameraUpscaleFilters.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>
/// The scheduling mechanism to apply upscaling.
/// </summary>
public enum UpsamplerScheduleType
{
/// <summary>
/// Indicates that upscaling must happen before post processing.
/// This means that everything runs at the source resolution during rasterization, and post processes will
/// run at full resolution. Ideal for temporal upscalers.
/// </summary>
BeforePost,
/// <summary>
/// Indicates that upscaling should happen after depth of field but before other post processing.
/// This means that everything runs at the source resolution during rasterization and depth of field, and other post processes
/// will run at full resolution. More performant alternative for temporal upscalers at the expense of reduced image quality.
/// </summary>
AfterDepthOfField,
/// <summary>
/// Indicates that upscaling must happen after post processing.
/// This means that everything in the frame runs at the source resolution, and upscaling happens after
/// the final pass. This is ideal for spatial upscalers.
/// </summary>
AfterPost
}
private UpsamplerScheduleType m_UpsamplerSchedule = UpsamplerScheduleType.AfterPost;
/// <summary>
/// Property that sets / gets the state of the upscaling schedule.
/// This must be set at the beginning of the frame, once per camera.
/// </summary>
public UpsamplerScheduleType upsamplerSchedule { set { m_UpsamplerSchedule = value; } get { return m_UpsamplerSchedule; } }
/// <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;
m_UseMipBias = settings.useMipBias;
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;
// Check if a filter has been set via user API, if so we use that, otherwise we use the default from the GlobalDynamicResolutionSettings
bool hasUserRequestedFilter = s_CameraUpscaleFilters.TryGetValue(s_ActiveCameraId, out DynamicResUpscaleFilter requestedFilter);
filter = hasUserRequestedFilter ? requestedFilter : 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;
}
/// <summary>
/// Gets the resolved scale
/// </summary>
/// <returns>The resolved scale in form of <see cref="Vector2"/></returns>
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>
/// Returns the mip bias to apply in the rendering pipeline. This mip bias helps bring detail since sampling of textures occurs at the target rate.
/// </summary>
/// <param name="inputResolution">The input width x height resolution in pixels.</param>
/// <param name="outputResolution">The output width x height resolution in pixels.</param>
/// <param name="forceApply">False by default. If true, we ignore the useMipBias setting and return a mip bias regardless.</param>
/// <returns>The calculated mip bias</returns>
public float CalculateMipBias(Vector2Int inputResolution, Vector2Int outputResolution, bool forceApply = false)
{
if (!m_UseMipBias && !forceApply)
return 0.0f;
return (float)Math.Log((double)inputResolution.x / (double)outputResolution.x, 2.0);
}
/// <summary>
/// Set the scaler method used to drive dynamic resolution by the user.
/// </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_ScalerContainers[(int)DynamicResScalerSlot.User] = new ScalerContainer() { type = scalerType, method = scaler };
}
/// <summary>
/// Set the scaler method used to drive dynamic resolution internally from the Scriptable Rendering Pipeline. This function should only be called by Scriptable Rendering Pipeline.
/// </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 SetSystemDynamicResScaler(PerformDynamicRes scaler, DynamicResScalePolicyType scalerType = DynamicResScalePolicyType.ReturnsMinMaxLerpFactor)
{
s_ScalerContainers[(int)DynamicResScalerSlot.System] = new ScalerContainer() { type = scalerType, method = scaler };
}
/// <summary>
/// Sets the active dynamic scaler slot to be used by the runtime when calculating frame resolutions.
/// See DynamicResScalerSlot for more information.
/// </summary>
/// <param name="slot">The scaler to be selected and used by the runtime.</param>
static public void SetActiveDynamicScalerSlot(DynamicResScalerSlot slot)
{
s_ActiveScalerSlot = slot;
}
/// <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 the Upscale filter used by the camera when dynamic resolution is run.
/// </summary>
/// <param name="camera">The camera for which the upscale filter is set.</param>
/// <param name="filter">The filter to be used by the camera to upscale to final resolution.</param>
static public void SetUpscaleFilter(Camera camera, DynamicResUpscaleFilter filter)
{
var cameraID = camera.GetInstanceID();
if (s_CameraUpscaleFilters.ContainsKey(cameraID))
{
s_CameraUpscaleFilters[cameraID] = filter;
}
else
{
s_CameraUpscaleFilters.Add(cameraID, filter);
}
}
/// <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)
{
FlushScalableBufferManagerState();
s_ActiveInstanceDirty = false;
return;
}
if (!m_ForcingRes)
{
ref ScalerContainer scaler = ref s_ScalerContainers[(int)s_ActiveScalerSlot];
if (scaler.type == DynamicResScalePolicyType.ReturnsMinMaxLerpFactor)
{
float currLerp = scaler.method();
float lerpFactor = Mathf.Clamp(currLerp, 0.0f, 1.0f);
m_CurrentFraction = Mathf.Lerp(m_MinScreenFraction, m_MaxScreenFraction, lerpFactor);
}
else if (scaler.type == DynamicResScalePolicyType.ReturnsPercentage)
{
float percentageRequested = Mathf.Max(scaler.method(), 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 || runUpscalerFilterOnFullResolution) && (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()
{
//we assume that the DRS schedule takes care of anti aliasing. Thus we dont care if the fraction requested is 1.0
return m_CurrentCameraRequest && m_Enabled && (m_CurrentFraction != 1.0f || runUpscalerFilterOnFullResolution);
}
/// <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, and the output is always smaller or equal then the input.
/// </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.
/// This function uses the internal resolved scale from 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)
{
return ApplyScalesOnSize(size, GetResolvedScale());
}
internal Vector2Int ApplyScalesOnSize(Vector2Int size, Vector2 scales)
{
Vector2Int scaledSize = new Vector2Int(Mathf.CeilToInt(size.x * scales.x), Mathf.CeilToInt(size.y * scales.y));
if (m_ForceSoftwareFallback || type != DynamicResolutionType.Hardware)
{
scaledSize.x += (1 & scaledSize.x);
scaledSize.y += (1 & scaledSize.y);
}
scaledSize.x = Math.Min(scaledSize.x, size.x);
scaledSize.y = Math.Min(scaledSize.y, size.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;
}
/// <summary>
/// Returns the resolved low res multiplier based on the low res transparency threshold settings.
/// Note: The pipeline can use this to drive the scale for low res transparency if available.
/// </summary>
/// <param name="targetLowRes"> the target low resolution.
/// If by any chance thresholding is disabled or clamped, the exact same resolution is returned.
/// This allows the caller to directly compare the float result safely with the floating point target resolution.
/// </param>
/// <returns>Returns the resolved low res multiplier based on the low transparency threshold settings.</returns>
public float GetLowResMultiplier(float targetLowRes)
{
if (!m_Enabled)
return targetLowRes;
float thresholdPercentage = Math.Min(m_CachedSettings.lowResTransparencyMinimumThreshold / 100.0f, targetLowRes);
float targetPercentage = targetLowRes * m_CurrentFraction;
if (targetPercentage >= thresholdPercentage)
return targetLowRes;
return Mathf.Clamp(thresholdPercentage / m_CurrentFraction, 0.0f, 1.0f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43b290740b0e7c34ea95e0fd8b38eb68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,135 @@
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. Obsolete and not supported.
/// </summary>
[Obsolete("Bilinear upscale filter is considered obsolete and is not supported anymore, please use CatmullRom for a very cheap, but blurry filter.", false)] Bilinear,
/// <summary>
/// Bicubic Catmull-Rom upscaling filter.
/// </summary>
CatmullRom,
/// <summary>
/// Lanczos upscaling filter. Obsolete and not supported.
/// </summary>
[Obsolete("Lanczos upscale filter is considered obsolete and is not supported anymore, please use Contrast Adaptive Sharpening for very sharp filter or FidelityFX Super Resolution 1.0.", false)] Lanczos,
/// <summary>
/// Contrast Adaptive Sharpening upscaling filter.
/// </summary>
ContrastAdaptiveSharpen,
/// <summary>
/// FidelityFX Super Resolution 1.0
/// </summary>
[InspectorName("FidelityFX Super Resolution 1.0")]
EdgeAdaptiveScalingUpres,
/// <summary>
/// Temporal Upscaling.
/// </summary>
[InspectorName("TAA Upscale")]
TAAU
}
/// <summary>User-facing settings for dynamic resolution.</summary>
[Serializable]
public struct GlobalDynamicResolutionSettings
{
/// <summary>Default GlobalDynamicResolutionSettings</summary>
/// <returns></returns>
public static GlobalDynamicResolutionSettings NewDefault() => new GlobalDynamicResolutionSettings()
{
useMipBias = false,
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,
lowResTransparencyMinimumThreshold = 0.0f,
rayTracingHalfResThreshold = 50.0f,
// Defaults for dlss
enableDLSS = false,
DLSSUseOptimalSettings = true,
DLSSPerfQualitySetting = 0,
DLSSSharpness = 0.5f,
DLSSInjectionPoint = DynamicResolutionHandler.UpsamplerScheduleType.BeforePost,
fsrOverrideSharpness = false,
fsrSharpness = FSRUtils.kDefaultSharpnessLinear
};
/// <summary>Select whether the dynamic resolution is enabled or not.</summary>
public bool enabled;
/// <summary>Offsets the mip bias to recover mode detail. This only works if the camera is utilizing TAA.</summary>
public bool useMipBias;
/// <summary>Toggle NVIDIA Deep Learning Super Sampling (DLSS).</summary>
public bool enableDLSS;
/// <summary>Opaque quality setting of NVIDIA Deep Learning Super Sampling (DLSS). Use the system enum UnityEngine.NVIDIA.DLSSQuality to set the quality.</summary>
public uint DLSSPerfQualitySetting;
/// <summary>The injection point at which to apply DLSS upscaling.</summary>
public DynamicResolutionHandler.UpsamplerScheduleType DLSSInjectionPoint;
/// <summary>Toggle NVIDIA Deep Learning Super Sampling (DLSS) automatic recommendation system for scaling and sharpness.
/// If this is on, the manually established scale callback for Dynamic Resolution Scaling is ignored. The sharpness setting of DLSS is also ignored.
/// </summary>
public bool DLSSUseOptimalSettings;
/// <summary>Pixel sharpness of NVIDIA Deep Leraning Super Sampling (DLSS).</summary>
[Range(0, 1)]
public float DLSSSharpness;
/// <summary>Toggle sharpness override for AMD FidelityFX Super Resolution (FSR).
/// If this is on, a sharpness value specified by the user will be used instead of the default.
/// </summary>
public bool fsrOverrideSharpness;
/// <summary>Pixel sharpness of AMD FidelityFX Super Resolution (FSR).</summary>
[Range(0, 1)]
public float fsrSharpness;
/// <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 default of upscaling filter used. It can be overridden via the API DynamicResolutionHandler.SetUpscaleFilter </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;
/// <summary>The minimum percentage threshold allowed to clamp low resolution transparency. When the resolution percentage falls below this threshold, HDRP will clamp the low resolution to this percentage.</summary>
public float lowResTransparencyMinimumThreshold;
/// <summary>The minimum percentage threshold allowed to render ray tracing effects at half resolution. When the resolution percentage falls below this threshold, HDRP will render ray tracing effects at full resolution.</summary>
public float rayTracingHalfResThreshold;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 602e65923ac15cd498c65c02be421511
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace UnityEngine.Rendering
{
/// <summary>
/// Interface to identify additional data components
/// </summary>
public interface IAdditionalData
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 387193679abe1894fa3e4edf840e8932
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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,11 @@
fileFormatVersion: 2
guid: abbe08f5688bd7342b997cf075044945
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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,3 @@
fileFormatVersion: 2
guid: 188d5dc897b64646b3757571725337ce
timeCreated: 1591792904

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,11 @@
fileFormatVersion: 2
guid: fa6940bea9f1edf4ea661175c511ca11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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,11 @@
fileFormatVersion: 2
guid: 1511526f1e7c8e94ea477c5b5c9dcc32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace UnityEngine.Rendering
{
/// <summary>
/// A set of extension methods for collections
/// </summary>
public static class RemoveRangeExtensions
{
/// <summary>
/// Tries to remove a range of elements from the list in the given range.
/// </summary>
/// <param name="list">The list to remove the range</param>
/// <param name="index">The zero-based starting index of the range of elements to remove</param>
/// <param name="count">The number of elements to remove.</param>
/// <param name="error">The exception raised by the implementation</param>
/// <typeparam name="TValue">The value type stored on the list</typeparam>
/// <returns>True if succeed, false otherwise</returns>
[CollectionAccess(CollectionAccessType.ModifyExistingContent)]
[MustUseReturnValue]
public static bool TryRemoveElementsInRange<TValue>([DisallowNull] this IList<TValue> list, int index, int count, [NotNullWhen(false)] out Exception error)
{
try
{
if (list is List<TValue> genericList)
{
genericList.RemoveRange(index, count);
}
else
{
if (index < 0) throw new ArgumentOutOfRangeException(nameof(index));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (list.Count - index < count) throw new ArgumentException("index and count do not denote a valid range of elements in the list");
for (var i = count; i > 0; --i)
list.RemoveAt(index);
}
}
catch (Exception e)
{
error = e;
return false;
}
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c578537bcf35184a95f154d606e2b9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
namespace UnityEngine.Rendering
{
using System;
using UnityEngine;
/// <summary>
/// Class to serizalize Enum as string and recover it's state
/// </summary>
[Serializable]
public class SerializableEnum
{
[SerializeField] private string m_EnumValueAsString;
[SerializeField] private string m_EnumTypeAsString;
/// <summary> Value as enum </summary>
public Enum value
{
get => !string.IsNullOrEmpty(m_EnumTypeAsString) && Enum.TryParse(Type.GetType(m_EnumTypeAsString), m_EnumValueAsString, out object result) ? (Enum)result : default;
set => m_EnumValueAsString = value.ToString();
}
/// <summary>
/// Construct an enum to be serialized with a type
/// </summary>
/// <param name="enumType">The underliying type of the enum</param>
public SerializableEnum(Type enumType)
{
m_EnumTypeAsString = enumType.AssemblyQualifiedName;
m_EnumValueAsString = Enum.GetNames(enumType)[0];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bbce3ae0d13a4240a1d7531d1e00b3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="K">The key type</typeparam>
/// <typeparam name="V">The value</typeparam>
/// <example>
/// public sealed class MyDictionary : SerializedDictionary&lt;KeyType, ValueType&gt; {}
/// </example>
[Serializable]
public class SerializedDictionary<K, V> : SerializedDictionary<K, V, K, V>
{
/// <summary>
/// Conversion to serialize a key
/// </summary>
/// <param name="key">The key to serialize</param>
/// <returns>The Key that has been serialized</returns>
public override K SerializeKey(K key) => key;
/// <summary>
/// Conversion to serialize a value
/// </summary>
/// <param name="val">The value</param>
/// <returns>The value</returns>
public override V SerializeValue(V val) => val;
/// <summary>
/// Conversion to serialize a key
/// </summary>
/// <param name="key">The key to serialize</param>
/// <returns>The Key that has been serialized</returns>
public override K DeserializeKey(K key) => key;
/// <summary>
/// Conversion to serialize a value
/// </summary>
/// <param name="val">The value</param>
/// <returns>The value</returns>
public override V DeserializeValue(V val) => val;
}
/// <summary>
/// Dictionary that can serialize keys and values as other types
/// </summary>
/// <typeparam name="K">The key type</typeparam>
/// <typeparam name="V">The value type</typeparam>
/// <typeparam name="SK">The type which the key will be serialized for</typeparam>
/// <typeparam name="SV">The type which the value will be serialized for</typeparam>
[Serializable]
public abstract class SerializedDictionary<K, V, SK, SV> : Dictionary<K, V>, ISerializationCallbackReceiver
{
[SerializeField]
List<SK> m_Keys = new List<SK>();
[SerializeField]
List<SV> m_Values = new List<SV>();
/// <summary>
/// From <see cref="K"/> to <see cref="SK"/>
/// </summary>
/// <param name="key">They key in <see cref="K"/></param>
/// <returns>The key in <see cref="SK"/></returns>
public abstract SK SerializeKey(K key);
/// <summary>
/// From <see cref="V"/> to <see cref="SV"/>
/// </summary>
/// <param name="value">The value in <see cref="V"/></param>
/// <returns>The value in <see cref="SV"/></returns>
public abstract SV SerializeValue(V value);
/// <summary>
/// From <see cref="SK"/> to <see cref="K"/>
/// </summary>
/// <param name="serializedKey">They key in <see cref="SK"/></param>
/// <returns>The key in <see cref="K"/></returns>
public abstract K DeserializeKey(SK serializedKey);
/// <summary>
/// From <see cref="SV"/> to <see cref="V"/>
/// </summary>
/// <param name="serializedValue">The value in <see cref="SV"/></param>
/// <returns>The value in <see cref="V"/></returns>
public abstract V DeserializeValue(SV serializedValue);
/// <summary>
/// OnBeforeSerialize implementation.
/// </summary>
public void OnBeforeSerialize()
{
m_Keys.Clear();
m_Values.Clear();
foreach (var kvp in this)
{
m_Keys.Add(SerializeKey(kvp.Key));
m_Values.Add(SerializeValue(kvp.Value));
}
}
/// <summary>
/// OnAfterDeserialize implementation.
/// </summary>
public void OnAfterDeserialize()
{
for (int i = 0; i < m_Keys.Count; i++)
Add(DeserializeKey(m_Keys[i]), DeserializeValue(m_Values[i]));
m_Keys.Clear();
m_Values.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b21ca88b322a62d4b9a511ddabc12251
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace UnityEngine.Rendering
{
/// <summary>
/// A set of extension methods for collections
/// </summary>
public static class SwapCollectionExtensions
{
/// <summary>
/// Tries to remove a range of elements from the list in the given range.
/// </summary>
/// <param name="list">The list to remove the range</param>
/// <param name="from">From index</param>
/// <param name="to">To index</param>
/// <param name="error">The exception raised by the implementation</param>
/// <typeparam name="TValue">The value type stored on the list</typeparam>
/// <returns>True if succeed, false otherwise</returns>
[CollectionAccess(CollectionAccessType.ModifyExistingContent)]
[MustUseReturnValue]
public static bool TrySwap<TValue>([DisallowNull] this IList<TValue> list, int from, int to, [NotNullWhen(false)] out Exception error)
{
error = null;
if (list == null)
{
error = new ArgumentNullException(nameof(list));
}
else
{
if (from < 0 || from >= list.Count)
error = new ArgumentOutOfRangeException(nameof(from));
if (to < 0 || to >= list.Count)
error = new ArgumentOutOfRangeException(nameof(to));
}
if (error != null)
return false;
// https://tearth.dev/posts/performance-of-the-different-ways-to-swap-two-values/
(list[to], list[from]) = (list[from], list[to]);
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 57a16f5659fb09d4e833f26df49cd9c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5133c1dbdb17436449b17269e791cb3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: