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,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")]
[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Runtime.Tests")]
[assembly: InternalsVisibleTo("Unity.RenderPipelines.HighDefinition.Runtime")]

View File

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

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e99921594db38ce4cbb616f0ca87148d
folderAsset: yes
timeCreated: 1488370960
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
using UnityEngine;
namespace UnityEngine.Rendering
{
/// <summary>
/// Utility component allowing users to setup many different static camera and cycle through their positions using the Debug Window.
/// </summary>
[CoreRPHelpURLAttribute("Camera-Switcher")]
public class CameraSwitcher : MonoBehaviour
{
/// <summary>
/// List of target cameras.
/// </summary>
public Camera[] m_Cameras;
private int m_CurrentCameraIndex = -1;
private Camera m_OriginalCamera = null;
private Vector3 m_OriginalCameraPosition;
private Quaternion m_OriginalCameraRotation;
private Camera m_CurrentCamera = null;
GUIContent[] m_CameraNames = null;
int[] m_CameraIndices = null;
DebugUI.EnumField m_DebugEntry;
//saved enum fields for when repainting
int m_DebugEntryEnumIndex;
void OnEnable()
{
m_OriginalCamera = GetComponent<Camera>();
m_CurrentCamera = m_OriginalCamera;
if (m_OriginalCamera == null)
{
Debug.LogError("Camera Switcher needs a Camera component attached");
return;
}
m_CurrentCameraIndex = GetCameraCount() - 1;
m_CameraNames = new GUIContent[GetCameraCount()];
m_CameraIndices = new int[GetCameraCount()];
for (int i = 0; i < m_Cameras.Length; ++i)
{
Camera cam = m_Cameras[i];
if (cam != null)
{
m_CameraNames[i] = new GUIContent(cam.name);
}
else
{
m_CameraNames[i] = new GUIContent("null");
}
m_CameraIndices[i] = i;
}
m_CameraNames[GetCameraCount() - 1] = new GUIContent("Original Camera");
m_CameraIndices[GetCameraCount() - 1] = GetCameraCount() - 1;
m_DebugEntry = new DebugUI.EnumField { displayName = "Camera Switcher", getter = () => m_CurrentCameraIndex, setter = value => SetCameraIndex(value), enumNames = m_CameraNames, enumValues = m_CameraIndices, getIndex = () => m_DebugEntryEnumIndex, setIndex = value => m_DebugEntryEnumIndex = value };
var panel = DebugManager.instance.GetPanel("Camera", true);
panel.children.Add(m_DebugEntry);
}
void OnDisable()
{
if (m_DebugEntry != null && m_DebugEntry.panel != null)
{
var panel = m_DebugEntry.panel;
panel.children.Remove(m_DebugEntry);
}
}
int GetCameraCount()
{
return m_Cameras.Length + 1; // We need +1 for handling the original camera.
}
Camera GetNextCamera()
{
if (m_CurrentCameraIndex == m_Cameras.Length)
return m_OriginalCamera;
else
return m_Cameras[m_CurrentCameraIndex];
}
void SetCameraIndex(int index)
{
if (index > 0 && index < GetCameraCount())
{
m_CurrentCameraIndex = index;
if (m_CurrentCamera == m_OriginalCamera)
{
m_OriginalCameraPosition = m_OriginalCamera.transform.position;
m_OriginalCameraRotation = m_OriginalCamera.transform.rotation;
}
m_CurrentCamera = GetNextCamera();
if (m_CurrentCamera != null)
{
// If we witch back to the original camera, put back the transform in it.
if (m_CurrentCamera == m_OriginalCamera)
{
m_OriginalCamera.transform.position = m_OriginalCameraPosition;
m_OriginalCamera.transform.rotation = m_OriginalCameraRotation;
}
transform.position = m_CurrentCamera.transform.position;
transform.rotation = m_CurrentCamera.transform.rotation;
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f52d1e5345c395641b482217c8804ff9
timeCreated: 1488380874
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,203 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.Rendering
{
/// <summary>
/// Utility Free Camera component.
/// </summary>
[CoreRPHelpURLAttribute("Free-Camera")]
public class FreeCamera : MonoBehaviour
{
const float k_MouseSensitivityMultiplier = 0.01f;
/// <summary>
/// Rotation speed when using a controller.
/// </summary>
public float m_LookSpeedController = 120f;
/// <summary>
/// Rotation speed when using the mouse.
/// </summary>
public float m_LookSpeedMouse = 4.0f;
/// <summary>
/// Movement speed.
/// </summary>
public float m_MoveSpeed = 10.0f;
/// <summary>
/// Value added to the speed when incrementing.
/// </summary>
public float m_MoveSpeedIncrement = 2.5f;
/// <summary>
/// Scale factor of the turbo mode.
/// </summary>
public float m_Turbo = 10.0f;
#if !USE_INPUT_SYSTEM
private static string kMouseX = "Mouse X";
private static string kMouseY = "Mouse Y";
private static string kRightStickX = "Controller Right Stick X";
private static string kRightStickY = "Controller Right Stick Y";
private static string kVertical = "Vertical";
private static string kHorizontal = "Horizontal";
private static string kYAxis = "YAxis";
private static string kSpeedAxis = "Speed Axis";
#endif
#if USE_INPUT_SYSTEM
InputAction lookAction;
InputAction moveAction;
InputAction speedAction;
InputAction yMoveAction;
#endif
void OnEnable()
{
RegisterInputs();
}
void RegisterInputs()
{
#if USE_INPUT_SYSTEM
var map = new InputActionMap("Free Camera");
lookAction = map.AddAction("look", binding: "<Mouse>/delta");
moveAction = map.AddAction("move", binding: "<Gamepad>/leftStick");
speedAction = map.AddAction("speed", binding: "<Gamepad>/dpad");
yMoveAction = map.AddAction("yMove");
lookAction.AddBinding("<Gamepad>/rightStick").WithProcessor("scaleVector2(x=15, y=15)");
moveAction.AddCompositeBinding("Dpad")
.With("Up", "<Keyboard>/w")
.With("Up", "<Keyboard>/upArrow")
.With("Down", "<Keyboard>/s")
.With("Down", "<Keyboard>/downArrow")
.With("Left", "<Keyboard>/a")
.With("Left", "<Keyboard>/leftArrow")
.With("Right", "<Keyboard>/d")
.With("Right", "<Keyboard>/rightArrow");
speedAction.AddCompositeBinding("Dpad")
.With("Up", "<Keyboard>/home")
.With("Down", "<Keyboard>/end");
yMoveAction.AddCompositeBinding("Dpad")
.With("Up", "<Keyboard>/pageUp")
.With("Down", "<Keyboard>/pageDown")
.With("Up", "<Keyboard>/e")
.With("Down", "<Keyboard>/q")
.With("Up", "<Gamepad>/rightshoulder")
.With("Down", "<Gamepad>/leftshoulder");
moveAction.Enable();
lookAction.Enable();
speedAction.Enable();
yMoveAction.Enable();
#endif
#if UNITY_EDITOR && !USE_INPUT_SYSTEM
List<InputManagerEntry> inputEntries = new List<InputManagerEntry>();
// Add new bindings
inputEntries.Add(new InputManagerEntry { name = kRightStickX, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Fourth, sensitivity = 1.0f, gravity = 1.0f, deadZone = 0.2f });
inputEntries.Add(new InputManagerEntry { name = kRightStickY, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Fifth, sensitivity = 1.0f, gravity = 1.0f, deadZone = 0.2f, invert = true });
inputEntries.Add(new InputManagerEntry { name = kYAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "page up", altBtnPositive = "joystick button 5", btnNegative = "page down", altBtnNegative = "joystick button 4", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f });
inputEntries.Add(new InputManagerEntry { name = kYAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "q", btnNegative = "e", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f });
inputEntries.Add(new InputManagerEntry { name = kSpeedAxis, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "home", btnNegative = "end", gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f });
inputEntries.Add(new InputManagerEntry { name = kSpeedAxis, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Seventh, gravity = 1000.0f, deadZone = 0.001f, sensitivity = 1000.0f });
InputRegistering.RegisterInputs(inputEntries);
#endif
}
float inputRotateAxisX, inputRotateAxisY;
float inputChangeSpeed;
float inputVertical, inputHorizontal, inputYAxis;
bool leftShiftBoost, leftShift, fire1;
void UpdateInputs()
{
inputRotateAxisX = 0.0f;
inputRotateAxisY = 0.0f;
leftShiftBoost = false;
fire1 = false;
#if USE_INPUT_SYSTEM
var lookDelta = lookAction.ReadValue<Vector2>();
inputRotateAxisX = lookDelta.x * m_LookSpeedMouse * k_MouseSensitivityMultiplier;
inputRotateAxisY = lookDelta.y * m_LookSpeedMouse * k_MouseSensitivityMultiplier;
leftShift = Keyboard.current?.leftShiftKey?.isPressed ?? false;
fire1 = Mouse.current?.leftButton?.isPressed == true || Gamepad.current?.xButton?.isPressed == true;
inputChangeSpeed = speedAction.ReadValue<Vector2>().y;
var moveDelta = moveAction.ReadValue<Vector2>();
inputVertical = moveDelta.y;
inputHorizontal = moveDelta.x;
inputYAxis = yMoveAction.ReadValue<Vector2>().y;
#else
if (Input.GetMouseButton(1))
{
leftShiftBoost = true;
inputRotateAxisX = Input.GetAxis(kMouseX) * m_LookSpeedMouse;
inputRotateAxisY = Input.GetAxis(kMouseY) * m_LookSpeedMouse;
}
inputRotateAxisX += (Input.GetAxis(kRightStickX) * m_LookSpeedController * k_MouseSensitivityMultiplier);
inputRotateAxisY += (Input.GetAxis(kRightStickY) * m_LookSpeedController * k_MouseSensitivityMultiplier);
leftShift = Input.GetKey(KeyCode.LeftShift);
fire1 = Input.GetAxis("Fire1") > 0.0f;
inputChangeSpeed = Input.GetAxis(kSpeedAxis);
inputVertical = Input.GetAxis(kVertical);
inputHorizontal = Input.GetAxis(kHorizontal);
inputYAxis = Input.GetAxis(kYAxis);
#endif
}
void Update()
{
// If the debug menu is running, we don't want to conflict with its inputs.
if (DebugManager.instance.displayRuntimeUI)
return;
UpdateInputs();
if (inputChangeSpeed != 0.0f)
{
m_MoveSpeed += inputChangeSpeed * m_MoveSpeedIncrement;
if (m_MoveSpeed < m_MoveSpeedIncrement) m_MoveSpeed = m_MoveSpeedIncrement;
}
bool moved = inputRotateAxisX != 0.0f || inputRotateAxisY != 0.0f || inputVertical != 0.0f || inputHorizontal != 0.0f || inputYAxis != 0.0f;
if (moved)
{
float rotationX = transform.localEulerAngles.x;
float newRotationY = transform.localEulerAngles.y + inputRotateAxisX;
// Weird clamping code due to weird Euler angle mapping...
float newRotationX = (rotationX - inputRotateAxisY);
if (rotationX <= 90.0f && newRotationX >= 0.0f)
newRotationX = Mathf.Clamp(newRotationX, 0.0f, 90.0f);
if (rotationX >= 270.0f)
newRotationX = Mathf.Clamp(newRotationX, 270.0f, 360.0f);
transform.localRotation = Quaternion.Euler(newRotationX, newRotationY, transform.localEulerAngles.z);
float moveSpeed = Time.deltaTime * m_MoveSpeed;
if (fire1 || leftShiftBoost && leftShift)
moveSpeed *= m_Turbo;
transform.position += transform.forward * moveSpeed * inputVertical;
transform.position += transform.right * moveSpeed * inputHorizontal;
transform.position += Vector3.up * moveSpeed * inputYAxis;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 618b0e3f6c65dd247a4a016150006c57
timeCreated: 1488370968
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e58bd4f13d26cde488393d3a2e9bf9cd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 58a02e8711d94134393b2a8ac22b96ca
folderAsset: yes
timeCreated: 1491989728
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Templated class for <see cref="IDebugDisplaySettings"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class DebugDisplaySettings<T> : IDebugDisplaySettings
where T : IDebugDisplaySettings, new()
{
/// <summary>
/// The set of <see cref="IDebugDisplaySettingsData"/> containing the settings for this debug display
/// </summary>
protected readonly HashSet<IDebugDisplaySettingsData> m_Settings = new HashSet<IDebugDisplaySettingsData>();
private static readonly Lazy<T> s_Instance = new Lazy<T>(() =>
{
var instance = new T();
instance.Reset();
return instance;
});
/// <summary>
/// The singleton instance that contains the current settings of Rendering Debugger.
/// </summary>
public static T Instance => s_Instance.Value;
#region IDebugDisplaySettingsQuery
/// <summary>
/// Returns true if any of the debug settings are currently active.
/// </summary>
public virtual bool AreAnySettingsActive
{
get
{
foreach (IDebugDisplaySettingsData setting in m_Settings)
{
if (setting.AreAnySettingsActive)
return true;
}
return false;
}
}
/// <summary>
/// Checks whether the current state of these settings allows post-processing.
/// </summary>
public virtual bool IsPostProcessingAllowed
{
get
{
// Only enable post-processing if we aren't using certain debug-views.
bool postProcessingAllowed = true;
foreach (IDebugDisplaySettingsData setting in m_Settings)
postProcessingAllowed &= setting.IsPostProcessingAllowed;
return postProcessingAllowed;
}
}
/// <summary>
/// Returns true if lighting is active for current state of debug settings.
/// </summary>
public virtual bool IsLightingActive
{
get
{
bool lightingActive = true;
foreach (IDebugDisplaySettingsData setting in m_Settings)
lightingActive &= setting.IsLightingActive;
return lightingActive;
}
}
#endregion
/// <summary>
/// Adds a new <see cref="TData"/> to this settings
/// </summary>
/// <typeparam name="TData">The type of <see cref="TData"/> to be added</typeparam>
/// <param name="newData">The <see cref="TData"/> to be added</param>
/// <returns>The type of <see cref="TData"/> that has been added</returns>
protected TData Add<TData>(TData newData) where TData : IDebugDisplaySettingsData
{
m_Settings.Add(newData);
return newData;
}
/// <summary>
/// Executes an action for each element
/// </summary>
/// <param name="onExecute"></param>
public void ForEach(Action<IDebugDisplaySettingsData> onExecute)
{
foreach (IDebugDisplaySettingsData setting in m_Settings)
{
onExecute(setting);
}
}
/// <summary>
/// Reset the stored debug settings
/// </summary>
public virtual void Reset()
{
m_Settings.Clear();
}
/// <summary>
/// Attempts to get the color that should be used to clear the screen according to current debug settings.
/// </summary>
/// <param name="color">A reference to the screen clear color to use.</param>
/// <returns>True if the color reference was updated, and false otherwise.</returns>
public virtual bool TryGetScreenClearColor(ref Color color)
{
foreach (IDebugDisplaySettingsData setting in m_Settings)
{
if (setting.TryGetScreenClearColor(ref color))
return true;
}
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace UnityEngine.Rendering
{
/// <summary>
/// The abstract common implementation of the <see cref="IDebugDisplaySettingsPanelDisposable"/>
/// </summary>
public abstract class DebugDisplaySettingsPanel : IDebugDisplaySettingsPanelDisposable
{
private readonly List<DebugUI.Widget> m_Widgets = new List<DebugUI.Widget>();
private readonly DisplayInfoAttribute m_DisplayInfo;
/// <summary>
/// The Panel name
/// </summary>
public virtual string PanelName => m_DisplayInfo?.name ?? string.Empty;
/// <summary>
/// The order where this panel should be shown
/// </summary>
public virtual int Order => m_DisplayInfo?.order ?? 0;
/// <summary>
/// The collection of widgets that are in this panel
/// </summary>
public DebugUI.Widget[] Widgets => m_Widgets.ToArray();
/// <summary>
/// The <see cref="DebugUI.Flags"/> for this panel
/// </summary>
public virtual DebugUI.Flags Flags => DebugUI.Flags.None;
/// <summary>
/// Adds a widget to the panel
/// </summary>
/// <param name="widget">The <see cref="DebugUI.Widget"/> to be added.</param>
protected void AddWidget(DebugUI.Widget widget)
{
if (widget == null)
throw new ArgumentNullException(nameof(widget));
m_Widgets.Add(widget);
}
/// <summary>
/// Clears the widgets list
/// </summary>
protected void Clear()
{
m_Widgets.Clear();
}
/// <summary>
/// Disposes the panel
/// </summary>
public void Dispose()
{
Clear();
}
/// <summary>
/// Default constructor
/// </summary>
protected DebugDisplaySettingsPanel()
{
m_DisplayInfo = GetType().GetCustomAttribute<DisplayInfoAttribute>();
if (m_DisplayInfo == null)
Debug.Log($"Type {GetType()} should specify the attribute {nameof(DisplayInfoAttribute)}");
}
}
/// <summary>
/// Class to help declare rendering debugger panels
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class DebugDisplaySettingsPanel<T> : DebugDisplaySettingsPanel
where T : IDebugDisplaySettingsData
{
internal T m_Data;
/// <summary>
/// Access to the data stored
/// </summary>
public T data
{
get => m_Data;
internal set => m_Data = value;
}
/// <summary>
/// Default constructor
/// </summary>
/// <param name="data">The data that the panel holds</param>
protected DebugDisplaySettingsPanel(T data)
: base()
{
m_Data = data;
}
}
}

View File

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

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// The UI implementation for a debug settings panel
/// </summary>
public class DebugDisplaySettingsUI : IDebugData
{
private IEnumerable<IDebugDisplaySettingsPanelDisposable> m_DisposablePanels;
private IDebugDisplaySettings m_Settings;
private void Reset()
{
if (m_Settings != null)
{
m_Settings.Reset();
// TODO: Tear the UI down and re-create it for now - this is horrible, so reset it instead.
UnregisterDebug();
RegisterDebug(m_Settings);
DebugManager.instance.RefreshEditor();
}
}
/// <summary>
/// Register a display for the UI
/// </summary>
/// <param name="settings"><see cref="IDebugDisplaySettings"/> to be registered</param>
public void RegisterDebug(IDebugDisplaySettings settings)
{
DebugManager debugManager = DebugManager.instance;
List<IDebugDisplaySettingsPanelDisposable> panels = new List<IDebugDisplaySettingsPanelDisposable>();
debugManager.RegisterData(this);
m_Settings = settings;
m_DisposablePanels = panels;
Action<IDebugDisplaySettingsData> onExecute = (data) =>
{
IDebugDisplaySettingsPanelDisposable disposableSettingsPanel = data.CreatePanel();
DebugUI.Widget[] panelWidgets = disposableSettingsPanel.Widgets;
DebugUI.Panel panel = debugManager.GetPanel(
displayName: disposableSettingsPanel.PanelName,
createIfNull: true,
groupIndex: (disposableSettingsPanel is DebugDisplaySettingsPanel debugDisplaySettingsPanel) ? debugDisplaySettingsPanel.Order : 0);
ObservableList<DebugUI.Widget> panelChildren = panel.children;
panel.flags = disposableSettingsPanel.Flags;
panels.Add(disposableSettingsPanel);
panelChildren.Add(panelWidgets);
};
m_Settings.ForEach(onExecute);
}
/// <summary>
/// Unregister the debug panels
/// </summary>
public void UnregisterDebug()
{
DebugManager debugManager = DebugManager.instance;
foreach (IDebugDisplaySettingsPanelDisposable disposableSettingsPanel in m_DisposablePanels)
{
DebugUI.Widget[] panelWidgets = disposableSettingsPanel.Widgets;
string panelId = disposableSettingsPanel.PanelName;
DebugUI.Panel panel = debugManager.GetPanel(panelId, true);
ObservableList<DebugUI.Widget> panelChildren = panel.children;
disposableSettingsPanel.Dispose();
panelChildren.Remove(panelWidgets);
}
m_DisposablePanels = null;
debugManager.UnregisterData(this);
}
#region IDebugData
/// <summary>
/// The reset action to be executed when a Reset of the rendering debugger is need
/// </summary>
/// <returns>A <see cref="Action"/> with the restet callback</returns>
public Action GetReset()
{
return Reset;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
namespace UnityEngine.Rendering
{
/// <summary>
/// Debug Dispaly Settings Volume
/// </summary>
public class DebugDisplaySettingsVolume : IDebugDisplaySettingsData
{
/// <summary>Current volume debug settings.</summary>
public IVolumeDebugSettings2 volumeDebugSettings { get; }
/// <summary>
/// Constructor with the settings
/// </summary>
/// <param name="volumeDebugSettings"></param>
public DebugDisplaySettingsVolume(IVolumeDebugSettings2 volumeDebugSettings)
{
this.volumeDebugSettings = volumeDebugSettings;
}
internal int volumeComponentEnumIndex;
static class Styles
{
public static readonly GUIContent none = new GUIContent("None");
public static readonly GUIContent editorCamera = new GUIContent("Editor Camera");
}
static class Strings
{
public static readonly string none = "None";
public static readonly string camera = "Camera";
public static readonly string parameter = "Parameter";
public static readonly string component = "Component";
public static readonly string debugViewNotSupported = "Debug view not supported";
public static readonly string volumeInfo = "Volume Info";
public static readonly string interpolatedValue = "Interpolated Value";
public static readonly string defaultValue = "Default Value";
public static readonly string global = "Global";
public static readonly string local = "Local";
}
internal static class WidgetFactory
{
public static DebugUI.EnumField CreateComponentSelector(SettingsPanel panel, Action<DebugUI.Field<int>, int> refresh)
{
int componentIndex = 0;
var componentNames = new List<GUIContent>() { Styles.none };
var componentValues = new List<int>() { componentIndex++ };
foreach (var type in panel.data.volumeDebugSettings.volumeComponentsPathAndType)
{
componentNames.Add(new GUIContent() { text = type.Item1 });
componentValues.Add(componentIndex++);
}
return new DebugUI.EnumField
{
displayName = Strings.component,
getter = () => panel.data.volumeDebugSettings.selectedComponent,
setter = value => panel.data.volumeDebugSettings.selectedComponent = value,
enumNames = componentNames.ToArray(),
enumValues = componentValues.ToArray(),
getIndex = () => panel.data.volumeComponentEnumIndex,
setIndex = value => { panel.data.volumeComponentEnumIndex = value; },
onValueChanged = refresh
};
}
public static DebugUI.ObjectPopupField CreateCameraSelector(SettingsPanel panel, Action<DebugUI.Field<Object>, Object> refresh)
{
return new DebugUI.ObjectPopupField
{
displayName = Strings.camera,
getter = () => panel.data.volumeDebugSettings.selectedCamera,
setter = value =>
{
var c = panel.data.volumeDebugSettings.cameras.ToArray();
panel.data.volumeDebugSettings.selectedCameraIndex = Array.IndexOf(c, value as Camera);
},
getObjects = () => panel.data.volumeDebugSettings.cameras,
onValueChanged = refresh
};
}
static DebugUI.Widget CreateVolumeParameterWidget(string name, VolumeParameter param, Func<bool> isHiddenCallback = null)
{
if (param == null)
return new DebugUI.Value() { displayName = name, getter = () => "-" };
var parameterType = param.GetType();
// Special overrides
if (parameterType == typeof(ColorParameter))
{
var p = (ColorParameter)param;
return new DebugUI.ColorField()
{
displayName = name,
hdr = p.hdr,
showAlpha = p.showAlpha,
getter = () => p.value,
setter = value => p.value = value,
isHiddenCallback = isHiddenCallback
};
}
else if (parameterType == typeof(BoolParameter))
{
var p = (BoolParameter)param;
return new DebugUI.BoolField()
{
displayName = name,
getter = () => p.value,
setter = value => p.value = value,
isHiddenCallback = isHiddenCallback
};
}
else
{
var typeInfo = parameterType.GetTypeInfo();
var genericArguments = typeInfo.BaseType.GenericTypeArguments;
if (genericArguments.Length > 0 && genericArguments[0].IsArray)
{
return new DebugUI.ObjectListField()
{
displayName = name,
getter = () => (Object[])parameterType.GetProperty("value").GetValue(param, null),
type = parameterType
};
}
}
// For parameters that do not override `ToString`
var property = param.GetType().GetProperty("value");
var toString = property.PropertyType.GetMethod("ToString", Type.EmptyTypes);
if ((toString == null) || (toString.DeclaringType == typeof(object)) || (toString.DeclaringType == typeof(UnityEngine.Object)))
{
// Check if the parameter has a name
var nameProp = property.PropertyType.GetProperty("name");
if (nameProp == null)
return new DebugUI.Value() { displayName = name, getter = () => Strings.debugViewNotSupported };
// Return the parameter name
return new DebugUI.Value()
{
displayName = name,
getter = () =>
{
var value = property.GetValue(param);
if (value == null || value.Equals(null))
return Strings.none;
var valueString = nameProp.GetValue(value);
return valueString ?? Strings.none;
},
isHiddenCallback = isHiddenCallback
};
}
// Call the ToString method
return new DebugUI.Value()
{
displayName = name,
getter = () =>
{
var value = property.GetValue(param);
return value == null ? Strings.none : value.ToString();
},
isHiddenCallback = isHiddenCallback
};
}
public static DebugUI.Table CreateVolumeTable(DebugDisplaySettingsVolume data)
{
var table = new DebugUI.Table()
{
displayName = Strings.parameter,
isReadOnly = true
};
Type selectedType = data.volumeDebugSettings.selectedComponentType;
if (selectedType == null)
return table;
var stack = data.volumeDebugSettings.selectedCameraVolumeStack ?? VolumeManager.instance.stack;
var stackComponent = stack.GetComponent(selectedType);
if (stackComponent == null)
return table;
var volumes = data.volumeDebugSettings.GetVolumes();
var inst = (VolumeComponent)ScriptableObject.CreateInstance(selectedType);
// First row for volume info
var row = new DebugUI.Table.Row()
{
displayName = Strings.volumeInfo,
opened = true, // Open by default for the in-game view
children =
{
new DebugUI.Value()
{
displayName = Strings.interpolatedValue,
getter = () => string.Empty
}
}
};
// Second row, links to volume gameobjects
var row2 = new DebugUI.Table.Row()
{
displayName = "GameObject",
children = { new DebugUI.Value() { getter = () => string.Empty } }
};
foreach (var volume in volumes)
{
var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile;
row.children.Add(new DebugUI.Value()
{
displayName = profile.name,
getter = () =>
{
var scope = volume.isGlobal ? Strings.global : Strings.local;
var weight = data.volumeDebugSettings.GetVolumeWeight(volume);
return scope + " (" + (weight * 100f) + "%)";
}
});
row2.children.Add(new DebugUI.ObjectField()
{
displayName = profile.name,
getter = () => volume,
});
}
row.children.Add(new DebugUI.Value() { displayName = Strings.defaultValue, getter = () => string.Empty });
table.children.Add(row);
row2.children.Add(new DebugUI.Value() { getter = () => string.Empty });
table.children.Add(row2);
// Build rows - recursively handles nested parameters
var rows = new List<DebugUI.Table.Row>();
int AddParameterRows(Type type, string baseName = null, int skip = 0)
{
void AddRow(FieldInfo f, string prefix, int skip)
{
var fieldName = prefix + f.Name;
var attr = (DisplayInfoAttribute[])f.GetCustomAttributes(typeof(DisplayInfoAttribute), true);
if (attr.Length != 0)
fieldName = prefix + attr[0].name;
#if UNITY_EDITOR
// Would be nice to have the equivalent for the runtime debug.
else
fieldName = UnityEditor.ObjectNames.NicifyVariableName(fieldName);
#endif
int currentParam = rows.Count + skip;
row = new DebugUI.Table.Row()
{
displayName = fieldName,
children = { CreateVolumeParameterWidget(Strings.interpolatedValue, stackComponent.parameterList[currentParam]) },
};
foreach (var volume in volumes)
{
VolumeParameter param = null;
var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile;
if (profile.TryGet(selectedType, out VolumeComponent component))
param = component.parameterList[currentParam];
row.children.Add(CreateVolumeParameterWidget(volume.name + " (" + profile.name + ")", param, () => !component.parameterList[currentParam].overrideState));
}
row.children.Add(CreateVolumeParameterWidget(Strings.defaultValue, inst.parameterList[currentParam]));
rows.Add(row);
}
var fields = type
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.OrderBy(t => t.MetadataToken);
foreach (var field in fields)
{
if (field.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length != 0)
{
skip++;
continue;
}
var fieldType = field.FieldType;
if (fieldType.IsSubclassOf(typeof(VolumeParameter)))
AddRow(field, baseName ?? string.Empty, skip);
else if (!fieldType.IsArray && fieldType.IsClass)
skip += AddParameterRows(fieldType, baseName ?? (field.Name + " "), skip);
}
return skip;
}
AddParameterRows(selectedType);
foreach (var r in rows.OrderBy(t => t.displayName))
table.children.Add(r);
data.volumeDebugSettings.RefreshVolumes(volumes);
for (int i = 0; i < volumes.Length; i++)
table.SetColumnVisibility(i + 1, data.volumeDebugSettings.VolumeHasInfluence(volumes[i]));
float timer = 0.0f, refreshRate = 0.2f;
table.isHiddenCallback = () =>
{
timer += Time.deltaTime;
if (timer >= refreshRate)
{
if (data.volumeDebugSettings.selectedCamera != null)
{
var newVolumes = data.volumeDebugSettings.GetVolumes();
if (!data.volumeDebugSettings.RefreshVolumes(newVolumes))
{
for (int i = 0; i < newVolumes.Length; i++)
{
var visible = data.volumeDebugSettings.VolumeHasInfluence(newVolumes[i]);
table.SetColumnVisibility(i + 1, visible);
}
}
if (!volumes.SequenceEqual(newVolumes))
{
volumes = newVolumes;
DebugManager.instance.ReDrawOnScreenDebug();
}
}
timer = 0.0f;
}
return false;
};
return table;
}
}
[DisplayInfo(name = "Volume", order = int.MaxValue)]
internal class SettingsPanel : DebugDisplaySettingsPanel<DebugDisplaySettingsVolume>
{
public SettingsPanel(DebugDisplaySettingsVolume data)
: base(data)
{
AddWidget(WidgetFactory.CreateComponentSelector(this, (_, __) => Refresh()));
AddWidget(WidgetFactory.CreateCameraSelector(this, (_, __) => Refresh()));
}
DebugUI.Table m_VolumeTable = null;
void Refresh()
{
var panel = DebugManager.instance.GetPanel(PanelName);
if (panel == null)
return;
bool needsRefresh = false;
if (m_VolumeTable != null)
{
needsRefresh = true;
panel.children.Remove(m_VolumeTable);
}
if (m_Data.volumeDebugSettings.selectedComponent > 0 && m_Data.volumeDebugSettings.selectedCamera != null)
{
needsRefresh = true;
m_VolumeTable = WidgetFactory.CreateVolumeTable(m_Data);
AddWidget(m_VolumeTable);
panel.children.Add(m_VolumeTable);
}
if (needsRefresh)
DebugManager.instance.ReDrawOnScreenDebug();
}
}
#region IDebugDisplaySettingsData
/// <summary>
/// Checks whether ANY of the debug settings are currently active.
/// </summary>
public bool AreAnySettingsActive => false; // Volume Debug Panel doesn't need to modify the renderer data, therefore this property returns false
/// <summary>
/// Checks whether the current state of these settings allows post-processing.
/// </summary>
public bool IsPostProcessingAllowed => true;
/// <summary>
/// Checks whether lighting is active for these settings.
/// </summary>
public bool IsLightingActive => true;
/// <summary>
/// Attempts to get the color used to clear the screen for this debug setting.
/// </summary>
/// <param name="color">A reference to the screen clear color to use.</param>
/// <returns>"true" if we updated the color, "false" if we didn't change anything.</returns>
public bool TryGetScreenClearColor(ref Color color)
{
return false;
}
/// <summary>
/// Creates the panel
/// </summary>
/// <returns>The panel</returns>
public IDebugDisplaySettingsPanelDisposable CreatePanel()
{
return new SettingsPanel(this);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,213 @@
//#define RTPROFILER_DEBUG
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEngine.Rendering
{
/// <summary>
/// Debug frame timings class
/// </summary>
public class DebugFrameTiming
{
const string k_FpsFormatString = "{0:F1}";
const string k_MsFormatString = "{0:F2}ms";
const float k_RefreshRate = 1f / 5f;
internal FrameTimeSampleHistory m_FrameHistory;
internal BottleneckHistory m_BottleneckHistory;
/// <summary>
/// Size of the Bottleneck History Window in number of samples.
/// </summary>
public int bottleneckHistorySize { get; set; } = 60;
/// <summary>
/// Size of the Sample History Window in number of samples.
/// </summary>
public int sampleHistorySize { get; set; } = 30;
FrameTiming[] m_Timing = new FrameTiming[1];
FrameTimeSample m_Sample = new FrameTimeSample();
/// <summary>
/// Constructs the debug frame timing
/// </summary>
public DebugFrameTiming()
{
m_FrameHistory = new FrameTimeSampleHistory(sampleHistorySize);
m_BottleneckHistory = new BottleneckHistory(bottleneckHistorySize);
}
/// <summary>
/// Update timing data from profiling counters.
/// </summary>
public void UpdateFrameTiming()
{
m_Timing[0] = default;
m_Sample = default;
FrameTimingManager.CaptureFrameTimings();
FrameTimingManager.GetLatestTimings(1, m_Timing);
if (m_Timing.Length > 0)
{
m_Sample.FullFrameTime = (float)m_Timing.First().cpuFrameTime;
m_Sample.FramesPerSecond = m_Sample.FullFrameTime > 0f ? 1000f / m_Sample.FullFrameTime : 0f;
m_Sample.MainThreadCPUFrameTime = (float)m_Timing.First().cpuMainThreadFrameTime;
m_Sample.MainThreadCPUPresentWaitTime = (float)m_Timing.First().cpuMainThreadPresentWaitTime;
m_Sample.RenderThreadCPUFrameTime = (float)m_Timing.First().cpuRenderThreadFrameTime;
m_Sample.GPUFrameTime = (float)m_Timing.First().gpuFrameTime;
}
m_FrameHistory.DiscardOldSamples(sampleHistorySize);
m_FrameHistory.Add(m_Sample);
m_FrameHistory.ComputeAggregateValues();
m_BottleneckHistory.DiscardOldSamples(bottleneckHistorySize);
m_BottleneckHistory.AddBottleneckFromAveragedSample(m_FrameHistory.SampleAverage);
m_BottleneckHistory.ComputeHistogram();
}
/// <summary>
/// Add frame timing data widgets to debug UI.
/// </summary>
/// <param name="list">List of widgets to add the stats.</param>
public void RegisterDebugUI(List<DebugUI.Widget> list)
{
list.Add(new DebugUI.Foldout()
{
displayName = "Frame Stats",
opened = true,
columnLabels = new string[] { "Avg", "Min", "Max" },
children =
{
new DebugUI.ValueTuple
{
displayName = "Frame Rate (FPS)",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleAverage.FramesPerSecond },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMin.FramesPerSecond },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMax.FramesPerSecond },
}
},
new DebugUI.ValueTuple
{
displayName = "Frame Time",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.FullFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.FullFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.FullFrameTime },
}
},
new DebugUI.ValueTuple
{
displayName = "CPU Main Thread Frame",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUFrameTime },
}
},
new DebugUI.ValueTuple
{
displayName = "CPU Render Thread Frame",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.RenderThreadCPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.RenderThreadCPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.RenderThreadCPUFrameTime },
}
},
new DebugUI.ValueTuple
{
displayName = "CPU Present Wait",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUPresentWaitTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUPresentWaitTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUPresentWaitTime },
}
},
new DebugUI.ValueTuple
{
displayName = "GPU Frame",
values = new[]
{
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.GPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.GPUFrameTime },
new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.GPUFrameTime },
}
}
}
});
list.Add(new DebugUI.Foldout
{
displayName = "Bottlenecks",
children =
{
#if UNITY_EDITOR
new DebugUI.Container { displayName = "Not supported in Editor" }
#else
new DebugUI.ProgressBarValue { displayName = "CPU", getter = () => m_BottleneckHistory.Histogram.CPU },
new DebugUI.ProgressBarValue { displayName = "GPU", getter = () => m_BottleneckHistory.Histogram.GPU },
new DebugUI.ProgressBarValue { displayName = "Present limited", getter = () => m_BottleneckHistory.Histogram.PresentLimited },
new DebugUI.ProgressBarValue { displayName = "Balanced", getter = () => m_BottleneckHistory.Histogram.Balanced },
#endif
}
});
#if RTPROFILER_DEBUG
list.Add(new DebugUI.Foldout
{
displayName = "Realtime Profiler Debug",
children =
{
new DebugUI.IntField
{
displayName = "Frame Time Sample History Size",
getter = () => sampleHistorySize,
setter = (value) => { sampleHistorySize = value; },
min = () => 1,
max = () => 100
},
new DebugUI.IntField
{
displayName = "Bottleneck History Size",
getter = () => bottleneckHistorySize,
setter = (value) => { bottleneckHistorySize = value; },
min = () => 1,
max = () => 100
},
new DebugUI.IntField
{
displayName = "Force VSyncCount",
min = () => - 1,
max = () => 4,
getter = () => QualitySettings.vSyncCount,
setter = (value) => { QualitySettings.vSyncCount = value; }
},
new DebugUI.IntField
{
displayName = "Force TargetFrameRate",
min = () => - 1,
max = () => 1000,
getter = () => Application.targetFrameRate,
setter = (value) => { Application.targetFrameRate = value; }
},
}
});
#endif
}
internal void Reset()
{
m_BottleneckHistory.Clear();
m_FrameHistory.Clear();
}
}
}

View File

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

View File

@@ -0,0 +1,517 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.EnhancedTouch;
#endif
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
internal enum DebugAction
{
EnableDebugMenu,
PreviousDebugPanel,
NextDebugPanel,
Action,
MakePersistent,
MoveVertical,
MoveHorizontal,
Multiplier,
ResetAll,
DebugActionCount
}
enum DebugActionRepeatMode
{
Never,
Delay
}
public sealed partial class DebugManager
{
const string kEnableDebugBtn1 = "Enable Debug Button 1";
const string kEnableDebugBtn2 = "Enable Debug Button 2";
const string kDebugPreviousBtn = "Debug Previous";
const string kDebugNextBtn = "Debug Next";
const string kValidateBtn = "Debug Validate";
const string kPersistentBtn = "Debug Persistent";
const string kDPadVertical = "Debug Vertical";
const string kDPadHorizontal = "Debug Horizontal";
const string kMultiplierBtn = "Debug Multiplier";
const string kResetBtn = "Debug Reset";
const string kEnableDebug = "Enable Debug";
DebugActionDesc[] m_DebugActions;
DebugActionState[] m_DebugActionStates;
#if USE_INPUT_SYSTEM
InputActionMap debugActionMap = new InputActionMap("Debug Menu");
#endif
void RegisterActions()
{
m_DebugActions = new DebugActionDesc[(int)DebugAction.DebugActionCount];
m_DebugActionStates = new DebugActionState[(int)DebugAction.DebugActionCount];
var enableDebugMenu = new DebugActionDesc();
#if USE_INPUT_SYSTEM
enableDebugMenu.buttonAction = debugActionMap.FindAction(kEnableDebug);
#else
enableDebugMenu.buttonTriggerList.Add(new[] { kEnableDebugBtn1, kEnableDebugBtn2 });
enableDebugMenu.keyTriggerList.Add(new[] { KeyCode.LeftControl, KeyCode.Backspace });
#endif
enableDebugMenu.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.EnableDebugMenu, enableDebugMenu);
var resetDebugMenu = new DebugActionDesc();
#if USE_INPUT_SYSTEM
resetDebugMenu.buttonAction = debugActionMap.FindAction(kResetBtn);
#else
resetDebugMenu.keyTriggerList.Add(new[] { KeyCode.LeftAlt, KeyCode.Backspace });
resetDebugMenu.buttonTriggerList.Add(new[] { kResetBtn, kEnableDebugBtn2 });
#endif
resetDebugMenu.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.ResetAll, resetDebugMenu);
var nextDebugPanel = new DebugActionDesc();
#if USE_INPUT_SYSTEM
nextDebugPanel.buttonAction = debugActionMap.FindAction(kDebugNextBtn);
#else
nextDebugPanel.buttonTriggerList.Add(new[] { kDebugNextBtn });
#endif
nextDebugPanel.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.NextDebugPanel, nextDebugPanel);
var previousDebugPanel = new DebugActionDesc();
#if USE_INPUT_SYSTEM
previousDebugPanel.buttonAction = debugActionMap.FindAction(kDebugPreviousBtn);
#else
previousDebugPanel.buttonTriggerList.Add(new[] { kDebugPreviousBtn });
#endif
previousDebugPanel.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.PreviousDebugPanel, previousDebugPanel);
var validate = new DebugActionDesc();
#if USE_INPUT_SYSTEM
validate.buttonAction = debugActionMap.FindAction(kValidateBtn);
#else
validate.buttonTriggerList.Add(new[] { kValidateBtn });
#endif
validate.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.Action, validate);
var persistent = new DebugActionDesc();
#if USE_INPUT_SYSTEM
persistent.buttonAction = debugActionMap.FindAction(kPersistentBtn);
#else
persistent.buttonTriggerList.Add(new[] { kPersistentBtn });
#endif
persistent.repeatMode = DebugActionRepeatMode.Never;
AddAction(DebugAction.MakePersistent, persistent);
var multiplier = new DebugActionDesc();
#if USE_INPUT_SYSTEM
multiplier.buttonAction = debugActionMap.FindAction(kMultiplierBtn);
#else
multiplier.buttonTriggerList.Add(new[] { kMultiplierBtn });
#endif
multiplier.repeatMode = DebugActionRepeatMode.Delay;
validate.repeatDelay = 0f;
AddAction(DebugAction.Multiplier, multiplier);
var moveVertical = new DebugActionDesc();
#if USE_INPUT_SYSTEM
moveVertical.buttonAction = debugActionMap.FindAction(kDPadVertical);
#else
moveVertical.axisTrigger = kDPadVertical;
#endif
moveVertical.repeatMode = DebugActionRepeatMode.Delay;
moveVertical.repeatDelay = 0.16f;
AddAction(DebugAction.MoveVertical, moveVertical);
var moveHorizontal = new DebugActionDesc();
#if USE_INPUT_SYSTEM
moveHorizontal.buttonAction = debugActionMap.FindAction(kDPadHorizontal);
#else
moveHorizontal.axisTrigger = kDPadHorizontal;
#endif
moveHorizontal.repeatMode = DebugActionRepeatMode.Delay;
moveHorizontal.repeatDelay = 0.16f;
AddAction(DebugAction.MoveHorizontal, moveHorizontal);
}
internal void EnableInputActions()
{
#if USE_INPUT_SYSTEM
foreach (var action in debugActionMap)
action.Enable();
#endif
}
void AddAction(DebugAction action, DebugActionDesc desc)
{
int index = (int)action;
m_DebugActions[index] = desc;
m_DebugActionStates[index] = new DebugActionState();
}
void SampleAction(int actionIndex)
{
var desc = m_DebugActions[actionIndex];
var state = m_DebugActionStates[actionIndex];
// Disable all input events if we're using the new input system
#if USE_INPUT_SYSTEM
if (state.runningAction == false)
{
if (desc.buttonAction != null)
{
var value = desc.buttonAction.ReadValue<float>();
if (!Mathf.Approximately(value, 0))
state.TriggerWithButton(desc.buttonAction, value);
}
}
#elif ENABLE_LEGACY_INPUT_MANAGER
//bool canSampleAction = (state.actionTriggered == false) || (desc.repeatMode == DebugActionRepeatMode.Delay && state.timer > desc.repeatDelay);
if (state.runningAction == false)
{
// Check button triggers
for (int buttonListIndex = 0; buttonListIndex < desc.buttonTriggerList.Count; ++buttonListIndex)
{
var buttons = desc.buttonTriggerList[buttonListIndex];
bool allButtonPressed = true;
try
{
foreach (var button in buttons)
{
allButtonPressed = Input.GetButton(button);
if (!allButtonPressed)
break;
}
}
catch (ArgumentException)
{
// Exception thrown if the input mapping gets removed while in play mode (UUM-37148)
allButtonPressed = false;
}
if (allButtonPressed)
{
state.TriggerWithButton(buttons, 1f);
break;
}
}
// Check axis triggers
if (desc.axisTrigger != "")
{
try
{
float axisValue = Input.GetAxis(desc.axisTrigger);
if (axisValue != 0f)
state.TriggerWithAxis(desc.axisTrigger, axisValue);
}
catch (ArgumentException)
{
// Exception thrown if the input mapping gets removed while in play mode (UUM-37148)
}
}
// Check key triggers
for (int keyListIndex = 0; keyListIndex < desc.keyTriggerList.Count; ++keyListIndex)
{
bool allKeyPressed = true;
var keys = desc.keyTriggerList[keyListIndex];
try
{
foreach (var key in keys)
{
allKeyPressed = Input.GetKey(key);
if (!allKeyPressed)
break;
}
}
catch (ArgumentException)
{
// Exception thrown if the input mapping gets removed while in play mode (UUM-37148)
allKeyPressed = false;
}
if (allKeyPressed)
{
state.TriggerWithKey(keys, 1f);
break;
}
}
}
#endif
}
void UpdateAction(int actionIndex)
{
var desc = m_DebugActions[actionIndex];
var state = m_DebugActionStates[actionIndex];
if (state.runningAction)
state.Update(desc);
}
internal void UpdateActions()
{
for (int actionIndex = 0; actionIndex < m_DebugActions.Length; ++actionIndex)
{
UpdateAction(actionIndex);
SampleAction(actionIndex);
}
}
internal float GetAction(DebugAction action)
{
return m_DebugActionStates[(int)action].actionState;
}
internal bool GetActionToggleDebugMenuWithTouch()
{
#if USE_INPUT_SYSTEM
if (!EnhancedTouchSupport.enabled)
return false;
var touches = InputSystem.EnhancedTouch.Touch.activeTouches;
var touchCount = touches.Count;
InputSystem.TouchPhase? expectedTouchPhase = null;
#else
var touchCount = Input.touchCount;
TouchPhase? expectedTouchPhase = TouchPhase.Began;
#endif
if (touchCount == 3)
{
#if !USE_INPUT_SYSTEM
var touches = Input.touches; // Causes an allocation, which is why this is inside the condition
#endif
foreach (var touch in touches)
{
// Gesture: 3-finger double-tap
if ((!expectedTouchPhase.HasValue || touch.phase == expectedTouchPhase.Value) && touch.tapCount == 2)
return true;
}
}
return false;
}
internal bool GetActionReleaseScrollTarget()
{
#if USE_INPUT_SYSTEM
bool mouseWheelActive = Mouse.current != null && Mouse.current.scroll.ReadValue() != Vector2.zero;
bool touchSupported = Touchscreen.current != null;
#else
bool mouseWheelActive = Input.mouseScrollDelta != Vector2.zero;
bool touchSupported = Input.touchSupported;
#endif
return mouseWheelActive || touchSupported; // Touchscreens have general problems with scrolling, so it's disabled.
}
void RegisterInputs()
{
#if UNITY_EDITOR && !USE_INPUT_SYSTEM
var inputEntries = new List<InputManagerEntry>
{
new InputManagerEntry { name = kEnableDebugBtn1, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "left ctrl", altBtnPositive = "joystick button 8" },
new InputManagerEntry { name = kEnableDebugBtn2, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "backspace", altBtnPositive = "joystick button 9" },
new InputManagerEntry { name = kResetBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "left alt", altBtnPositive = "joystick button 1" },
new InputManagerEntry { name = kDebugNextBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "page down", altBtnPositive = "joystick button 5" },
new InputManagerEntry { name = kDebugPreviousBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "page up", altBtnPositive = "joystick button 4" },
new InputManagerEntry { name = kValidateBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "return", altBtnPositive = "joystick button 0" },
new InputManagerEntry { name = kPersistentBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "right shift", altBtnPositive = "joystick button 2" },
new InputManagerEntry { name = kMultiplierBtn, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "left shift", altBtnPositive = "joystick button 3" },
new InputManagerEntry { name = kDPadHorizontal, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "right", btnNegative = "left", gravity = 1000f, deadZone = 0.001f, sensitivity = 1000f },
new InputManagerEntry { name = kDPadVertical, kind = InputManagerEntry.Kind.KeyOrButton, btnPositive = "up", btnNegative = "down", gravity = 1000f, deadZone = 0.001f, sensitivity = 1000f },
new InputManagerEntry { name = kDPadVertical, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Seventh, btnPositive = "up", btnNegative = "down", gravity = 1000f, deadZone = 0.001f, sensitivity = 1000f },
new InputManagerEntry { name = kDPadHorizontal, kind = InputManagerEntry.Kind.Axis, axis = InputManagerEntry.Axis.Sixth, btnPositive = "right", btnNegative = "left", gravity = 1000f, deadZone = 0.001f, sensitivity = 1000f },
};
InputRegistering.RegisterInputs(inputEntries);
#endif
#if USE_INPUT_SYSTEM
// Register input system actions
var enableAction = debugActionMap.AddAction(kEnableDebug, type: InputActionType.Button);
enableAction.AddCompositeBinding("ButtonWithOneModifier")
.With("Modifier", "<Gamepad>/rightStickPress")
.With("Button", "<Gamepad>/leftStickPress")
.With("Modifier", "<Keyboard>/leftCtrl")
.With("Button", "<Keyboard>/backspace");
var resetAction = debugActionMap.AddAction(kResetBtn, type: InputActionType.Button);
resetAction.AddCompositeBinding("ButtonWithOneModifier")
.With("Modifier", "<Gamepad>/rightStickPress")
.With("Button", "<Gamepad>/b")
.With("Modifier", "<Keyboard>/leftAlt")
.With("Button", "<Keyboard>/backspace");
var next = debugActionMap.AddAction(kDebugNextBtn, type: InputActionType.Button);
next.AddBinding("<Keyboard>/pageDown");
next.AddBinding("<Gamepad>/rightShoulder");
var previous = debugActionMap.AddAction(kDebugPreviousBtn, type: InputActionType.Button);
previous.AddBinding("<Keyboard>/pageUp");
previous.AddBinding("<Gamepad>/leftShoulder");
var validateAction = debugActionMap.AddAction(kValidateBtn, type: InputActionType.Button);
validateAction.AddBinding("<Keyboard>/enter");
validateAction.AddBinding("<Gamepad>/a");
var persistentAction = debugActionMap.AddAction(kPersistentBtn, type: InputActionType.Button);
persistentAction.AddBinding("<Keyboard>/rightShift");
persistentAction.AddBinding("<Gamepad>/x");
var multiplierAction = debugActionMap.AddAction(kMultiplierBtn, type: InputActionType.Value);
multiplierAction.AddBinding("<Keyboard>/leftShift");
multiplierAction.AddBinding("<Gamepad>/y");
var moveVerticalAction = debugActionMap.AddAction(kDPadVertical);
moveVerticalAction.AddCompositeBinding("1DAxis")
.With("Positive", "<Gamepad>/dpad/up")
.With("Negative", "<Gamepad>/dpad/down")
.With("Positive", "<Keyboard>/upArrow")
.With("Negative", "<Keyboard>/downArrow");
var moveHorizontalAction = debugActionMap.AddAction(kDPadHorizontal);
moveHorizontalAction.AddCompositeBinding("1DAxis")
.With("Positive", "<Gamepad>/dpad/right")
.With("Negative", "<Gamepad>/dpad/left")
.With("Positive", "<Keyboard>/rightArrow")
.With("Negative", "<Keyboard>/leftArrow");
#endif
}
}
class DebugActionDesc
{
#if USE_INPUT_SYSTEM
public InputAction buttonAction = null;
#else
public string axisTrigger = "";
public List<string[]> buttonTriggerList = new List<string[]>();
public List<KeyCode[]> keyTriggerList = new List<KeyCode[]>();
#endif
public DebugActionRepeatMode repeatMode = DebugActionRepeatMode.Never;
public float repeatDelay;
}
class DebugActionState
{
enum DebugActionKeyType
{
Button,
Axis,
Key
}
DebugActionKeyType m_Type;
#if USE_INPUT_SYSTEM
InputAction inputAction;
#else
string[] m_PressedButtons;
string m_PressedAxis = "";
KeyCode[] m_PressedKeys;
#endif
bool[] m_TriggerPressedUp;
float m_Timer;
internal bool runningAction { get; private set; }
internal float actionState { get; private set; }
void Trigger(int triggerCount, float state)
{
actionState = state;
runningAction = true;
m_Timer = 0f;
m_TriggerPressedUp = new bool[triggerCount];
for (int i = 0; i < m_TriggerPressedUp.Length; ++i)
m_TriggerPressedUp[i] = false;
}
#if USE_INPUT_SYSTEM
public void TriggerWithButton(InputAction action, float state)
{
inputAction = action;
Trigger(action.bindings.Count, state);
}
#else
public void TriggerWithButton(string[] buttons, float state)
{
m_Type = DebugActionKeyType.Button;
m_PressedButtons = buttons;
m_PressedAxis = "";
Trigger(buttons.Length, state);
}
public void TriggerWithAxis(string axis, float state)
{
m_Type = DebugActionKeyType.Axis;
m_PressedAxis = axis;
Trigger(1, state);
}
public void TriggerWithKey(KeyCode[] keys, float state)
{
m_Type = DebugActionKeyType.Key;
m_PressedKeys = keys;
m_PressedAxis = "";
Trigger(keys.Length, state);
}
#endif
void Reset()
{
runningAction = false;
m_Timer = 0f;
m_TriggerPressedUp = null;
}
public void Update(DebugActionDesc desc)
{
// Always reset this so that the action can only be caught once until repeat/reset
actionState = 0f;
if (m_TriggerPressedUp != null)
{
m_Timer += Time.deltaTime;
for (int i = 0; i < m_TriggerPressedUp.Length; ++i)
{
#if USE_INPUT_SYSTEM
if (inputAction != null)
m_TriggerPressedUp[i] |= Mathf.Approximately(inputAction.ReadValue<float>(), 0f);
#else
if (m_Type == DebugActionKeyType.Button)
m_TriggerPressedUp[i] |= Input.GetButtonUp(m_PressedButtons[i]);
else if (m_Type == DebugActionKeyType.Axis)
m_TriggerPressedUp[i] |= Mathf.Approximately(Input.GetAxis(m_PressedAxis), 0f);
else
m_TriggerPressedUp[i] |= Input.GetKeyUp(m_PressedKeys[i]);
#endif
}
bool allTriggerUp = true;
foreach (bool value in m_TriggerPressedUp)
allTriggerUp &= value;
if (allTriggerUp || (m_Timer > desc.repeatDelay && desc.repeatMode == DebugActionRepeatMode.Delay))
Reset();
}
}
}
}

View File

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

View File

@@ -0,0 +1,150 @@
using System;
using System.Diagnostics;
using UnityEngine.Rendering.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_ANDROID || UNITY_IPHONE || UNITY_TVOS || UNITY_SWITCH
using UnityEngine.UI;
#endif
namespace UnityEngine.Rendering
{
using UnityObject = UnityEngine.Object;
public sealed partial class DebugManager
{
/// <summary>
/// The modes of the UI of the Rendering Debugger
/// </summary>
public enum UIMode : int
{
/// <summary>
/// Editor Window
/// </summary>
EditorMode,
/// <summary>
/// In Game view
/// </summary>
RuntimeMode
}
/// <summary>
/// Event that is raised when a window state is changed
/// </summary>
public static event Action<UIMode, bool> windowStateChanged;
class UIState
{
public UIMode mode;
[SerializeField]
private bool m_Open;
public bool open
{
get => m_Open;
set
{
if (m_Open == value)
return;
m_Open = value;
windowStateChanged?.Invoke(mode, m_Open);
}
}
}
private UIState editorUIState = new UIState() { mode = UIMode.EditorMode };
/// <summary>
/// Is the debug editor window open.
/// </summary>
public bool displayEditorUI
{
get => editorUIState.open;
set => editorUIState.open = value;
}
private bool m_EnableRuntimeUI = true;
/// <summary>
/// Controls whether runtime UI can be enabled. When this is set to false, there will be no overhead
/// from debug GameObjects or runtime initialization.
/// </summary>
public bool enableRuntimeUI
{
get => m_EnableRuntimeUI;
set
{
if (value != m_EnableRuntimeUI)
{
m_EnableRuntimeUI = value;
DebugUpdater.SetEnabled(value);
}
}
}
private UIState runtimeUIState = new UIState() { mode = UIMode.RuntimeMode };
/// <summary>
/// Displays the runtime version of the debug window.
/// </summary>
public bool displayRuntimeUI
{
get => m_Root != null && m_Root.activeInHierarchy;
set
{
if (value)
{
m_Root = UnityObject.Instantiate(Resources.Load<Transform>("DebugUICanvas")).gameObject;
m_Root.name = "[Debug Canvas]";
m_Root.transform.localPosition = Vector3.zero;
m_RootUICanvas = m_Root.GetComponent<DebugUIHandlerCanvas>();
#if UNITY_ANDROID || UNITY_IPHONE || UNITY_TVOS || UNITY_SWITCH
var canvasScaler = m_Root.GetComponent<CanvasScaler>();
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
#endif
m_Root.SetActive(true);
}
else
{
CoreUtils.Destroy(m_Root);
m_Root = null;
m_RootUICanvas = null;
}
onDisplayRuntimeUIChanged(value);
DebugUpdater.HandleInternalEventSystemComponents(value);
runtimeUIState.open = m_Root != null && m_Root.activeInHierarchy;
}
}
/// <summary>
/// Displays the persistent runtime debug window.
/// </summary>
public bool displayPersistentRuntimeUI
{
get => m_RootUIPersistentCanvas != null && m_PersistentRoot.activeInHierarchy;
set
{
if (value)
{
EnsurePersistentCanvas();
}
else
{
CoreUtils.Destroy(m_PersistentRoot);
m_PersistentRoot = null;
m_RootUIPersistentCanvas = null;
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,431 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using UnityEngine.Assertions;
using UnityEngine.Rendering.UI;
namespace UnityEngine.Rendering
{
using UnityObject = UnityEngine.Object;
/// <summary>
/// IDebugData interface.
/// </summary>
public interface IDebugData
{
/// <summary>Get the reset callback for this DebugData</summary>
/// <returns>The reset callback</returns>
Action GetReset();
//Action GetLoad();
//Action GetSave();
}
/// <summary>
/// Manager class for the Debug Window.
/// </summary>
public sealed partial class DebugManager
{
static readonly Lazy<DebugManager> s_Instance = new Lazy<DebugManager>(() => new DebugManager());
/// <summary>
/// Global instance of the DebugManager.
/// </summary>
public static DebugManager instance => s_Instance.Value;
ReadOnlyCollection<DebugUI.Panel> m_ReadOnlyPanels;
readonly List<DebugUI.Panel> m_Panels = new List<DebugUI.Panel>();
void UpdateReadOnlyCollection()
{
m_Panels.Sort();
m_ReadOnlyPanels = m_Panels.AsReadOnly();
}
/// <summary>
/// List of currently registered debug panels.
/// </summary>
public ReadOnlyCollection<DebugUI.Panel> panels
{
get
{
if (m_ReadOnlyPanels == null)
UpdateReadOnlyCollection();
return m_ReadOnlyPanels;
}
}
/// <summary>
/// Callback called when the runtime UI changed.
/// </summary>
public event Action<bool> onDisplayRuntimeUIChanged = delegate { };
/// <summary>
/// Callback called when the debug window is dirty.
/// </summary>
public event Action onSetDirty = delegate { };
event Action resetData;
/// <summary>
/// Force an editor request.
/// </summary>
public bool refreshEditorRequested;
int? m_RequestedPanelIndex;
GameObject m_Root;
DebugUIHandlerCanvas m_RootUICanvas;
GameObject m_PersistentRoot;
DebugUIHandlerPersistentCanvas m_RootUIPersistentCanvas;
/// <summary>
/// Is any debug window or UI currently active.
/// </summary>
public bool isAnyDebugUIActive
{
get
{
return
displayRuntimeUI || displayPersistentRuntimeUI
#if UNITY_EDITOR
|| displayEditorUI
#endif
;
}
}
DebugManager()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
RegisterInputs();
RegisterActions();
#endif
}
/// <summary>
/// Refresh the debug window.
/// </summary>
public void RefreshEditor()
{
refreshEditorRequested = true;
}
/// <summary>
/// Reset the debug window.
/// </summary>
public void Reset()
{
resetData?.Invoke();
ReDrawOnScreenDebug();
}
/// <summary>
/// Request the runtime debug UI be redrawn on the next update.
/// </summary>
public void ReDrawOnScreenDebug()
{
if (displayRuntimeUI)
m_RootUICanvas?.RequestHierarchyReset();
}
/// <summary>
/// Register debug data.
/// </summary>
/// <param name="data">Data to be registered.</param>
public void RegisterData(IDebugData data) => resetData += data.GetReset();
/// <summary>
/// Register debug data.
/// </summary>
/// <param name="data">Data to be registered.</param>
public void UnregisterData(IDebugData data) => resetData -= data.GetReset();
/// <summary>
/// Get hashcode state of the Debug Window.
/// </summary>
/// <returns></returns>
public int GetState()
{
int hash = 17;
foreach (var panel in m_Panels)
hash = hash * 23 + panel.GetHashCode();
return hash;
}
internal void RegisterRootCanvas(DebugUIHandlerCanvas root)
{
Assert.IsNotNull(root);
m_Root = root.gameObject;
m_RootUICanvas = root;
}
internal void ChangeSelection(DebugUIHandlerWidget widget, bool fromNext)
{
m_RootUICanvas.ChangeSelection(widget, fromNext);
}
internal void SetScrollTarget(DebugUIHandlerWidget widget)
{
if (m_RootUICanvas != null)
m_RootUICanvas.SetScrollTarget(widget);
}
void EnsurePersistentCanvas()
{
if (m_RootUIPersistentCanvas == null)
{
var uiManager = UnityObject.FindObjectOfType<DebugUIHandlerPersistentCanvas>();
if (uiManager == null)
{
m_PersistentRoot = UnityObject.Instantiate(Resources.Load<Transform>("DebugUIPersistentCanvas")).gameObject;
m_PersistentRoot.name = "[Debug Canvas - Persistent]";
m_PersistentRoot.transform.localPosition = Vector3.zero;
}
else
{
m_PersistentRoot = uiManager.gameObject;
}
m_RootUIPersistentCanvas = m_PersistentRoot.GetComponent<DebugUIHandlerPersistentCanvas>();
}
}
internal void TogglePersistent(DebugUI.Widget widget, int? forceTupleIndex = null)
{
if (widget == null)
return;
EnsurePersistentCanvas();
switch (widget)
{
case DebugUI.Value value:
m_RootUIPersistentCanvas.Toggle(value);
break;
case DebugUI.ValueTuple valueTuple:
m_RootUIPersistentCanvas.Toggle(valueTuple, forceTupleIndex);
break;
case DebugUI.Container container:
// When container is toggled, we make sure that if there are ValueTuples, they all get the same element index.
int pinnedIndex = container.children.Max(w => (w as DebugUI.ValueTuple)?.pinnedElementIndex ?? -1);
foreach (var child in container.children)
{
if (child is DebugUI.Value || child is DebugUI.ValueTuple)
TogglePersistent(child, pinnedIndex);
}
break;
default:
Debug.Log("Only readonly items can be made persistent.");
break;
}
}
void OnPanelDirty(DebugUI.Panel panel)
{
onSetDirty();
}
/// <summary>
/// Returns the panel index
/// </summary>
/// <param name="displayName">The displayname for the panel</param>
/// <returns>The index for the panel or -1 if not found.</returns>
public int PanelIndex([DisallowNull] string displayName)
{
displayName ??= string.Empty;
for (int i = 0; i < m_Panels.Count; ++i)
{
if (displayName.Equals(m_Panels[i].displayName, StringComparison.InvariantCultureIgnoreCase))
return i;
}
return -1;
}
/// <summary>
/// Request DebugWindow to open the specified panel.
/// </summary>
/// <param name="index">Index of the debug window panel to activate.</param>
public void RequestEditorWindowPanelIndex(int index)
{
// Similar to RefreshEditor(), this function is required to bypass a dependency problem where DebugWindow
// cannot be accessed from the Core.Runtime assembly. Should there be a better way to allow editor-dependent
// features in DebugUI?
m_RequestedPanelIndex = index;
}
internal int? GetRequestedEditorWindowPanelIndex()
{
int? requestedIndex = m_RequestedPanelIndex;
m_RequestedPanelIndex = null;
return requestedIndex;
}
// TODO: Optimally we should use a query path here instead of a display name
/// <summary>
/// Returns a debug panel.
/// </summary>
/// <param name="displayName">Name of the debug panel.</param>
/// <param name="createIfNull">Create the panel if it does not exists.</param>
/// <param name="groupIndex">Group index.</param>
/// <param name="overrideIfExist">Replace an existing panel.</param>
/// <returns></returns>
public DebugUI.Panel GetPanel(string displayName, bool createIfNull = false, int groupIndex = 0, bool overrideIfExist = false)
{
int panelIndex = PanelIndex(displayName);
DebugUI.Panel p = panelIndex >= 0 ? m_Panels[panelIndex] : null;
if (p != null)
{
if (overrideIfExist)
{
p.onSetDirty -= OnPanelDirty;
RemovePanel(p);
p = null;
}
else
return p;
}
if (createIfNull)
{
p = new DebugUI.Panel { displayName = displayName, groupIndex = groupIndex };
p.onSetDirty += OnPanelDirty;
m_Panels.Add(p);
UpdateReadOnlyCollection();
}
return p;
}
/// <summary>
/// Find the index of the panel from it's display name.
/// </summary>
/// <param name="displayName">The display name of the panel to find.</param>
/// <returns>The index of the panel in the list. -1 if not found.</returns>
public int FindPanelIndex(string displayName)
=> m_Panels.FindIndex(p => p.displayName == displayName);
// TODO: Use a query path here as well instead of a display name
/// <summary>
/// Remove a debug panel.
/// </summary>
/// <param name="displayName">Name of the debug panel to remove.</param>
public void RemovePanel(string displayName)
{
DebugUI.Panel panel = null;
foreach (var p in m_Panels)
{
if (p.displayName == displayName)
{
p.onSetDirty -= OnPanelDirty;
panel = p;
break;
}
}
RemovePanel(panel);
}
/// <summary>
/// Remove a debug panel.
/// </summary>
/// <param name="panel">Reference to the debug panel to remove.</param>
public void RemovePanel(DebugUI.Panel panel)
{
if (panel == null)
return;
m_Panels.Remove(panel);
UpdateReadOnlyCollection();
}
/// <summary>
/// Gets an <see cref="DebugUI.Widget[]"/> matching the given <see cref="DebugUI.Flags"/>
/// </summary>
/// <param name="flags">The flags of the widget</param>
/// <returns>Reference to the requested debug item.</returns>
public DebugUI.Widget[] GetItems(DebugUI.Flags flags)
{
using (ListPool<DebugUI.Widget>.Get(out var temp))
{
foreach (var panel in m_Panels)
{
var widgets = GetItemsFromContainer(flags, panel);
temp.AddRange(widgets);
}
return temp.ToArray();
}
}
internal DebugUI.Widget[] GetItemsFromContainer(DebugUI.Flags flags, DebugUI.IContainer container)
{
using (ListPool<DebugUI.Widget>.Get(out var temp))
{
foreach (var child in container.children)
{
if (child.flags.HasFlag(flags))
{
temp.Add(child);
continue;
}
if (child is DebugUI.IContainer containerChild)
{
temp.AddRange(GetItemsFromContainer(flags, containerChild));
}
}
return temp.ToArray();
}
}
/// <summary>
/// Get a Debug Item.
/// </summary>
/// <param name="queryPath">Path of the debug item.</param>
/// <returns>Reference to the requested debug item.</returns>
public DebugUI.Widget GetItem(string queryPath)
{
foreach (var panel in m_Panels)
{
var w = GetItem(queryPath, panel);
if (w != null)
return w;
}
return null;
}
/// <summary>
/// Get a debug item from a specific container.
/// </summary>
/// <param name="queryPath">Path of the debug item.</param>
/// <param name="container">Container to query.</param>
/// <returns>Reference to the requested debug item.</returns>
DebugUI.Widget GetItem(string queryPath, DebugUI.IContainer container)
{
foreach (var child in container.children)
{
if (child.queryPath == queryPath)
return child;
if (child is DebugUI.IContainer containerChild)
{
var w = GetItem(queryPath, containerChild);
if (w != null)
return w;
}
}
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,516 @@
namespace UnityEngine.Rendering
{
/// <summary>Debug class containing several debug shapes for debugging</summary>
public partial class DebugShapes
{
// Singleton
static DebugShapes s_Instance = null;
/// <summary>Singleton instance</summary>
static public DebugShapes instance
{
get
{
if (s_Instance == null)
{
s_Instance = new DebugShapes();
}
return s_Instance;
}
}
Mesh m_sphereMesh = null;
Mesh m_boxMesh = null;
Mesh m_coneMesh = null;
Mesh m_pyramidMesh = null;
// This code has been grabbed from http://wiki.unity3d.com/index.php/ProceduralPrimitives
void BuildSphere(ref Mesh outputMesh, float radius, uint longSubdiv, uint latSubdiv)
{
// Make sure it is empty before pushing anything to it
outputMesh.Clear();
// Build the vertices array
Vector3[] vertices = new Vector3[(longSubdiv + 1) * latSubdiv + 2];
float _pi = Mathf.PI;
float _2pi = _pi * 2f;
vertices[0] = Vector3.up * radius;
for (int lat = 0; lat < latSubdiv; lat++)
{
float a1 = _pi * (float)(lat + 1) / (latSubdiv + 1);
float sin1 = Mathf.Sin(a1);
float cos1 = Mathf.Cos(a1);
for (int lon = 0; lon <= longSubdiv; lon++)
{
float a2 = _2pi * (float)(lon == longSubdiv ? 0 : lon) / longSubdiv;
float sin2 = Mathf.Sin(a2);
float cos2 = Mathf.Cos(a2);
vertices[lon + lat * (longSubdiv + 1) + 1] = new Vector3(sin1 * cos2, cos1, sin1 * sin2) * radius;
}
}
vertices[vertices.Length - 1] = Vector3.up * -radius;
// Build the normals array
Vector3[] normals = new Vector3[vertices.Length];
for (int n = 0; n < vertices.Length; n++)
{
normals[n] = vertices[n].normalized;
}
// Build the UV array
Vector2[] uvs = new Vector2[vertices.Length];
uvs[0] = Vector2.up;
uvs[uvs.Length - 1] = Vector2.zero;
for (int lat = 0; lat < latSubdiv; lat++)
{
for (int lon = 0; lon <= longSubdiv; lon++)
{
uvs[lon + lat * (longSubdiv + 1) + 1] = new Vector2((float)lon / longSubdiv, 1f - (float)(lat + 1) / (latSubdiv + 1));
}
}
// Build the index array
int nbFaces = vertices.Length;
int nbTriangles = nbFaces * 2;
int nbIndexes = nbTriangles * 3;
int[] triangles = new int[nbIndexes];
// Top Cap
int i = 0;
for (int lon = 0; lon < longSubdiv; lon++)
{
triangles[i++] = lon + 2;
triangles[i++] = lon + 1;
triangles[i++] = 0;
}
//Middle
for (uint lat = 0; lat < latSubdiv - 1; lat++)
{
for (uint lon = 0; lon < longSubdiv; lon++)
{
uint current = lon + lat * (longSubdiv + 1) + 1;
uint next = current + longSubdiv + 1;
triangles[i++] = (int)current;
triangles[i++] = (int)current + 1;
triangles[i++] = (int)next + 1;
triangles[i++] = (int)current;
triangles[i++] = (int)next + 1;
triangles[i++] = (int)next;
}
}
// Bottom Cap
for (int lon = 0; lon < longSubdiv; lon++)
{
triangles[i++] = vertices.Length - 1;
triangles[i++] = vertices.Length - (lon + 2) - 1;
triangles[i++] = vertices.Length - (lon + 1) - 1;
}
// Assign them to
outputMesh.vertices = vertices;
outputMesh.normals = normals;
outputMesh.uv = uvs;
outputMesh.triangles = triangles;
outputMesh.RecalculateBounds();
}
void BuildBox(ref Mesh outputMesh, float length, float width, float height)
{
outputMesh.Clear();
Vector3 p0 = new Vector3(-length * .5f, -width * .5f, height * .5f);
Vector3 p1 = new Vector3(length * .5f, -width * .5f, height * .5f);
Vector3 p2 = new Vector3(length * .5f, -width * .5f, -height * .5f);
Vector3 p3 = new Vector3(-length * .5f, -width * .5f, -height * .5f);
Vector3 p4 = new Vector3(-length * .5f, width * .5f, height * .5f);
Vector3 p5 = new Vector3(length * .5f, width * .5f, height * .5f);
Vector3 p6 = new Vector3(length * .5f, width * .5f, -height * .5f);
Vector3 p7 = new Vector3(-length * .5f, width * .5f, -height * .5f);
Vector3[] vertices = new Vector3[]
{
// Bottom
p0, p1, p2, p3,
// Left
p7, p4, p0, p3,
// Front
p4, p5, p1, p0,
// Back
p6, p7, p3, p2,
// Right
p5, p6, p2, p1,
// Top
p7, p6, p5, p4
};
Vector3 up = Vector3.up;
Vector3 down = Vector3.down;
Vector3 front = Vector3.forward;
Vector3 back = Vector3.back;
Vector3 left = Vector3.left;
Vector3 right = Vector3.right;
Vector3[] normales = new Vector3[]
{
// Bottom
down, down, down, down,
// Left
left, left, left, left,
// Front
front, front, front, front,
// Back
back, back, back, back,
// Right
right, right, right, right,
// Top
up, up, up, up
};
Vector2 _00 = new Vector2(0f, 0f);
Vector2 _10 = new Vector2(1f, 0f);
Vector2 _01 = new Vector2(0f, 1f);
Vector2 _11 = new Vector2(1f, 1f);
Vector2[] uvs = new Vector2[]
{
// Bottom
_11, _01, _00, _10,
// Left
_11, _01, _00, _10,
// Front
_11, _01, _00, _10,
// Back
_11, _01, _00, _10,
// Right
_11, _01, _00, _10,
// Top
_11, _01, _00, _10,
};
int[] triangles = new int[]
{
// Bottom
3, 1, 0,
3, 2, 1,
// Left
3 + 4 * 1, 1 + 4 * 1, 0 + 4 * 1,
3 + 4 * 1, 2 + 4 * 1, 1 + 4 * 1,
// Front
3 + 4 * 2, 1 + 4 * 2, 0 + 4 * 2,
3 + 4 * 2, 2 + 4 * 2, 1 + 4 * 2,
// Back
3 + 4 * 3, 1 + 4 * 3, 0 + 4 * 3,
3 + 4 * 3, 2 + 4 * 3, 1 + 4 * 3,
// Right
3 + 4 * 4, 1 + 4 * 4, 0 + 4 * 4,
3 + 4 * 4, 2 + 4 * 4, 1 + 4 * 4,
// Top
3 + 4 * 5, 1 + 4 * 5, 0 + 4 * 5,
3 + 4 * 5, 2 + 4 * 5, 1 + 4 * 5,
};
outputMesh.vertices = vertices;
outputMesh.normals = normales;
outputMesh.uv = uvs;
outputMesh.triangles = triangles;
outputMesh.RecalculateBounds();
}
void BuildCone(ref Mesh outputMesh, float height, float topRadius, float bottomRadius, int nbSides)
{
outputMesh.Clear();
int nbVerticesCap = nbSides + 1;
// bottom + top + sides
Vector3[] vertices = new Vector3[nbVerticesCap + nbVerticesCap + nbSides * 2 + 2];
int vert = 0;
float _2pi = Mathf.PI * 2f;
// Bottom cap
vertices[vert++] = new Vector3(0f, 0f, 0f);
while (vert <= nbSides)
{
float rad = (float)vert / nbSides * _2pi;
vertices[vert] = new Vector3(Mathf.Sin(rad) * bottomRadius, Mathf.Cos(rad) * bottomRadius, 0f);
vert++;
}
// Top cap
vertices[vert++] = new Vector3(0f, 0f, height);
while (vert <= nbSides * 2 + 1)
{
float rad = (float)(vert - nbSides - 1) / nbSides * _2pi;
vertices[vert] = new Vector3(Mathf.Sin(rad) * topRadius, Mathf.Cos(rad) * topRadius, height);
vert++;
}
// Sides
int v = 0;
while (vert <= vertices.Length - 4)
{
float rad = (float)v / nbSides * _2pi;
vertices[vert] = new Vector3(Mathf.Sin(rad) * topRadius, Mathf.Cos(rad) * topRadius, height);
vertices[vert + 1] = new Vector3(Mathf.Sin(rad) * bottomRadius, Mathf.Cos(rad) * bottomRadius, 0);
vert += 2;
v++;
}
vertices[vert] = vertices[nbSides * 2 + 2];
vertices[vert + 1] = vertices[nbSides * 2 + 3];
// bottom + top + sides
Vector3[] normales = new Vector3[vertices.Length];
vert = 0;
// Bottom cap
while (vert <= nbSides)
{
normales[vert++] = new Vector3(0, 0, -1);
}
// Top cap
while (vert <= nbSides * 2 + 1)
{
normales[vert++] = new Vector3(0, 0, 1);
}
// Sides
v = 0;
while (vert <= vertices.Length - 4)
{
float rad = (float)v / nbSides * _2pi;
float cos = Mathf.Cos(rad);
float sin = Mathf.Sin(rad);
normales[vert] = new Vector3(sin, cos, 0f);
normales[vert + 1] = normales[vert];
vert += 2;
v++;
}
normales[vert] = normales[nbSides * 2 + 2];
normales[vert + 1] = normales[nbSides * 2 + 3];
Vector2[] uvs = new Vector2[vertices.Length];
// Bottom cap
int u = 0;
uvs[u++] = new Vector2(0.5f, 0.5f);
while (u <= nbSides)
{
float rad = (float)u / nbSides * _2pi;
uvs[u] = new Vector2(Mathf.Cos(rad) * .5f + .5f, Mathf.Sin(rad) * .5f + .5f);
u++;
}
// Top cap
uvs[u++] = new Vector2(0.5f, 0.5f);
while (u <= nbSides * 2 + 1)
{
float rad = (float)u / nbSides * _2pi;
uvs[u] = new Vector2(Mathf.Cos(rad) * .5f + .5f, Mathf.Sin(rad) * .5f + .5f);
u++;
}
// Sides
int u_sides = 0;
while (u <= uvs.Length - 4)
{
float t = (float)u_sides / nbSides;
uvs[u] = new Vector3(t, 1f);
uvs[u + 1] = new Vector3(t, 0f);
u += 2;
u_sides++;
}
uvs[u] = new Vector2(1f, 1f);
uvs[u + 1] = new Vector2(1f, 0f);
int nbTriangles = nbSides + nbSides + nbSides * 2;
int[] triangles = new int[nbTriangles * 3 + 3];
// Bottom cap
int tri = 0;
int i = 0;
while (tri < nbSides - 1)
{
triangles[i] = 0;
triangles[i + 1] = tri + 1;
triangles[i + 2] = tri + 2;
tri++;
i += 3;
}
triangles[i] = 0;
triangles[i + 1] = tri + 1;
triangles[i + 2] = 1;
tri++;
i += 3;
// Top cap
//tri++;
while (tri < nbSides * 2)
{
triangles[i] = tri + 2;
triangles[i + 1] = tri + 1;
triangles[i + 2] = nbVerticesCap;
tri++;
i += 3;
}
triangles[i] = nbVerticesCap + 1;
triangles[i + 1] = tri + 1;
triangles[i + 2] = nbVerticesCap;
tri++;
i += 3;
tri++;
// Sides
while (tri <= nbTriangles)
{
triangles[i] = tri + 2;
triangles[i + 1] = tri + 1;
triangles[i + 2] = tri + 0;
tri++;
i += 3;
triangles[i] = tri + 1;
triangles[i + 1] = tri + 2;
triangles[i + 2] = tri + 0;
tri++;
i += 3;
}
outputMesh.vertices = vertices;
outputMesh.normals = normales;
outputMesh.uv = uvs;
outputMesh.triangles = triangles;
outputMesh.RecalculateBounds();
}
void BuildPyramid(ref Mesh outputMesh, float width, float height, float depth)
{
outputMesh.Clear();
// Allocate the buffer
Vector3[] vertices = new Vector3[16];
// Top Face
vertices[0] = new Vector3(0f, 0f, 0f);
vertices[1] = new Vector3(-width / 2.0f, height / 2.0f, depth);
vertices[2] = new Vector3(width / 2.0f, height / 2.0f, depth);
// Left Face
vertices[3] = new Vector3(0f, 0f, 0f);
vertices[4] = new Vector3(width / 2.0f, height / 2.0f, depth);
vertices[5] = new Vector3(width / 2.0f, -height / 2.0f, depth);
// Bottom Face
vertices[6] = new Vector3(0f, 0f, 0f);
vertices[7] = new Vector3(width / 2.0f, -height / 2.0f, depth);
vertices[8] = new Vector3(-width / 2.0f, -height / 2.0f, depth);
// Right Face
vertices[9] = new Vector3(0f, 0f, 0f);
vertices[10] = new Vector3(-width / 2.0f, -height / 2.0f, depth);
vertices[11] = new Vector3(-width / 2.0f, height / 2.0f, depth);
// Cap
vertices[12] = new Vector3(-width / 2.0f, height / 2.0f, depth);
vertices[13] = new Vector3(-width / 2.0f, -height / 2.0f, depth);
vertices[14] = new Vector3(width / 2.0f, -height / 2.0f, depth);
vertices[15] = new Vector3(width / 2.0f, height / 2.0f, depth);
// TODO: support the uv/normals
Vector3[] normals = new Vector3[vertices.Length];
Vector2[] uvs = new Vector2[vertices.Length];
// The indexes for the side part is simple
int[] triangles = new int[18];
for (int idx = 0; idx < 12; ++idx)
{
triangles[idx] = idx;
}
// Cap indexes
triangles[12] = 12;
triangles[13] = 13;
triangles[14] = 14;
triangles[15] = 12;
triangles[16] = 14;
triangles[17] = 15;
outputMesh.vertices = vertices;
outputMesh.normals = normals;
outputMesh.uv = uvs;
outputMesh.triangles = triangles;
outputMesh.RecalculateBounds();
}
void BuildShapes()
{
m_sphereMesh = new Mesh();
BuildSphere(ref m_sphereMesh, 1.0f, 24, 16);
m_boxMesh = new Mesh();
BuildBox(ref m_boxMesh, 1.0f, 1.0f, 1.0f);
m_coneMesh = new Mesh();
BuildCone(ref m_coneMesh, 1.0f, 1.0f, 0.0f, 16);
m_pyramidMesh = new Mesh();
BuildPyramid(ref m_pyramidMesh, 1.0f, 1.0f, 1.0f);
}
void RebuildResources()
{
if (m_sphereMesh == null || m_boxMesh == null || m_coneMesh == null || m_pyramidMesh == null)
{
BuildShapes();
}
}
/// <summary>Get a Sphere Mesh</summary>
/// <returns>A Sphere Mesh</returns>
public Mesh RequestSphereMesh()
{
RebuildResources();
return m_sphereMesh;
}
/// <summary>Get a Box Mesh</summary>
/// <returns>A Box Mesh</returns>
public Mesh RequestBoxMesh()
{
RebuildResources();
return m_boxMesh;
}
/// <summary>Get a Cone Mesh</summary>
/// <returns>A Cone Mesh</returns>
public Mesh RequestConeMesh()
{
RebuildResources();
return m_coneMesh;
}
/// <summary>Get a Pyramid Mesh</summary>
/// <returns>A Pyramid Mesh</returns>
public Mesh RequestPyramidMesh()
{
RebuildResources();
return m_pyramidMesh;
}
}
}

View File

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

View File

@@ -0,0 +1,468 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace UnityEngine.Rendering
{
public partial class DebugUI
{
/// <summary>
/// Base class for "container" type widgets, although it can be used on its own (if a display name is set then it'll behave as a group with a header)
/// </summary>
public class Container : Widget, IContainer
{
const string k_IDToken = "#";
internal bool hideDisplayName => string.IsNullOrEmpty(displayName) || displayName.StartsWith(k_IDToken);
/// <summary>
/// List of children.
/// </summary>
public ObservableList<Widget> children { get; private set; }
/// <summary>
/// Panel the container is attached to.
/// </summary>
public override Panel panel
{
get { return m_Panel; }
internal set
{
/// Frequenlty used panels do now own widgets
if (value != null && value.flags.HasFlag(DebugUI.Flags.FrequentlyUsed))
return;
m_Panel = value;
// Bubble down
int numChildren = children.Count;
for (int i = 0; i < numChildren; i++)
children[i].panel = value;
}
}
/// <summary>
/// Constructor
/// </summary>
public Container()
: this(string.Empty, new ObservableList<Widget>())
{
}
/// <summary>
/// Constructor for a container without header
/// </summary>
/// <param name="id">The id of the container</param>
public Container(string id)
: this($"{k_IDToken}{id}", new ObservableList<Widget>())
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="displayName">Display name of the container.</param>
/// <param name="children">List of attached children.</param>
public Container(string displayName, ObservableList<Widget> children)
{
this.displayName = displayName;
this.children = children;
children.ItemAdded += OnItemAdded;
children.ItemRemoved += OnItemRemoved;
// Call OnAdded callback for already existing items to ensure their panel & parent are set
for (int i = 0; i < this.children.Count; i++)
OnItemAdded(this.children, new ListChangedEventArgs<Widget>(i, this.children[i]));
}
internal override void GenerateQueryPath()
{
base.GenerateQueryPath();
int numChildren = children.Count;
for (int i = 0; i < numChildren; i++)
children[i].GenerateQueryPath();
}
/// <summary>
/// Method called when a children is added.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of added children.</param>
protected virtual void OnItemAdded(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
if (e.item != null)
{
e.item.panel = m_Panel;
e.item.parent = this;
}
if (m_Panel != null)
m_Panel.SetDirty();
}
/// <summary>
/// Method called when a children is removed.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of removed children.</param>
protected virtual void OnItemRemoved(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
if (e.item != null)
{
e.item.panel = null;
e.item.parent = null;
}
if (m_Panel != null)
m_Panel.SetDirty();
}
/// <summary>
/// Returns the hash code of the widget.
/// </summary>
/// <returns>Hash code of the widget.</returns>
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + queryPath.GetHashCode();
hash = hash * 23 + isHidden.GetHashCode();
int numChildren = children.Count;
for (int i = 0; i < numChildren; i++)
hash = hash * 23 + children[i].GetHashCode();
return hash;
}
}
/// <summary>
/// Unity-like foldout that can be collapsed.
/// </summary>
public class Foldout : Container, IValueField
{
/// <summary>
/// Context menu item.
/// </summary>
public struct ContextMenuItem
{
/// <summary>
/// Name of the item displayed in context menu dropdown.
/// </summary>
public string displayName;
/// <summary>
/// Callback when context menu item is selected.
/// </summary>
public Action action;
}
/// <summary>
/// Always false.
/// </summary>
public bool isReadOnly { get { return false; } }
/// <summary>
/// Opened state of the foldout.
/// </summary>
public bool opened;
/// <summary>
/// Draw the foldout in full width using a header style.
/// </summary>
public bool isHeader;
/// <summary>
/// Optional list of context menu items. If the list is not provided, no context menu button will be displayed.
/// </summary>
public List<ContextMenuItem> contextMenuItems = null;
/// <summary>
/// List of columns labels.
/// </summary>
public string[] columnLabels { get; set; } = null;
/// <summary>
/// List of columns label tooltips.
/// </summary>
public string[] columnTooltips { get; set; } = null;
/// <summary>
/// Constructor.
/// </summary>
public Foldout() : base() { }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="displayName">Display name of the foldout.</param>
/// <param name="children">List of attached children.</param>
/// <param name="columnLabels">Optional list of column names.</param>
/// <param name="columnTooltips">Optional list of tooltips for column name labels.</param>
public Foldout(string displayName, ObservableList<Widget> children, string[] columnLabels = null, string[] columnTooltips = null)
: base(displayName, children)
{
this.columnLabels = columnLabels;
this.columnTooltips = columnTooltips;
}
/// <summary>
/// Get the opened state of the foldout.
/// </summary>
/// <returns>True if the foldout is opened.</returns>
public bool GetValue() => opened;
/// <summary>
/// Get the opened state of the foldout.
/// </summary>
/// <returns>True if the foldout is opened.</returns>
object IValueField.GetValue() => GetValue();
/// <summary>
/// Set the opened state of the foldout.
/// </summary>
/// <param name="value">True to open the foldout, false to close it.</param>
public void SetValue(object value) => SetValue((bool)value);
/// <summary>
/// Validates the value of the widget before setting it.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>The validated value.</returns>
public object ValidateValue(object value) => value;
/// <summary>
/// Set the value of the widget.
/// </summary>
/// <param name="value">Input value.</param>
public void SetValue(bool value) => opened = value;
}
/// <summary>
/// Horizontal Layout Container.
/// </summary>
public class HBox : Container
{
/// <summary>
/// Constructor.
/// </summary>
public HBox()
{
displayName = "HBox";
}
}
/// <summary>
/// Vertical Layout Container.
/// </summary>
public class VBox : Container
{
/// <summary>
/// Constructor.
/// </summary>
public VBox()
{
displayName = "VBox";
}
}
/// <summary>
/// Array Container.
/// </summary>
public class Table : Container
{
/// <summary>Row Container.</summary>
public class Row : Foldout
{
/// <summary>Constructor.</summary>
public Row() { displayName = "Row"; }
}
/// <summary>
/// True if the table is read only.
/// </summary>
public bool isReadOnly = false;
/// <summary>Constructor.</summary>
public Table() { displayName = "Array"; }
/// <summary>
/// Set column visibility.
/// </summary>
/// <param name="index">Index of the column.</param>
/// <param name="visible">True if the column should be visible.</param>
public void SetColumnVisibility(int index, bool visible)
{
#if UNITY_EDITOR
var header = Header;
if (index < 0 || index >= m_ColumnCount)
return;
index++;
if (header.IsColumnVisible(index) != visible)
{
var newVisibleColumns = new System.Collections.Generic.List<int>(header.state.visibleColumns);
if (newVisibleColumns.Contains(index))
{
newVisibleColumns.Remove(index);
}
else
{
newVisibleColumns.Add(index);
newVisibleColumns.Sort();
}
header.state.visibleColumns = newVisibleColumns.ToArray();
var cols = header.state.columns;
for (int i = 0; i < cols.Length; i++)
cols[i].width = 50f;
header.ResizeToFit();
}
#else
var columns = VisibleColumns;
if (index < 0 || index > columns.Length)
return;
columns[index] = visible;
#endif
}
/// <summary>
/// Get column visibility.
/// </summary>
/// <param name="index">Index of the column.</param>
/// <returns>True if the column is visible.</returns>
public bool GetColumnVisibility(int index)
{
#if UNITY_EDITOR
var header = Header;
if (index < 0 || index >= m_ColumnCount)
return false;
return header.IsColumnVisible(index + 1);
#else
var columns = VisibleColumns;
if (index < 0 || index > columns.Length)
return false;
return columns[index];
#endif
}
#if UNITY_EDITOR
/// <summary>
/// The scroll position of the table.
/// </summary>
public Vector2 scroll = Vector2.zero;
int m_ColumnCount;
UnityEditor.IMGUI.Controls.MultiColumnHeader m_Header = null;
/// <summary>
/// The table header for drawing
/// </summary>
public UnityEditor.IMGUI.Controls.MultiColumnHeader Header
{
get
{
if (m_Header != null)
return m_Header;
if (children.Count != 0)
{
m_ColumnCount = ((Container)children[0]).children.Count;
for (int i = 1; i < children.Count; i++)
{
if (((Container)children[i]).children.Count != m_ColumnCount)
{
Debug.LogError("All rows must have the same number of children.");
return null;
}
}
}
UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column CreateColumn(string name)
{
var col = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column()
{
canSort = false,
headerTextAlignment = TextAlignment.Center,
headerContent = new GUIContent(name),
};
GUIStyle style = UnityEditor.IMGUI.Controls.MultiColumnHeader.DefaultStyles.columnHeaderCenterAligned;
style.CalcMinMaxWidth(col.headerContent, out col.width, out float _);
col.width = Mathf.Min(col.width, 50f);
return col;
}
var cols = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column[m_ColumnCount + 1];
cols[0] = CreateColumn(displayName);
cols[0].allowToggleVisibility = false;
for (int i = 0; i < m_ColumnCount; i++)
cols[i + 1] = CreateColumn(((Container)children[0]).children[i].displayName);
var state = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState(cols);
m_Header = new UnityEditor.IMGUI.Controls.MultiColumnHeader(state) { height = 23 };
m_Header.ResizeToFit();
return m_Header;
}
}
#else
bool[] m_Header = null;
/// <summary>
/// The visible columns
/// </summary>
public bool[] VisibleColumns
{
get
{
if (m_Header != null)
return m_Header;
int columnCount = 0;
if (children.Count != 0)
{
columnCount = ((Container)children[0]).children.Count;
for (int i = 1; i < children.Count; i++)
{
if (((Container)children[i]).children.Count != columnCount)
{
Debug.LogError("All rows must have the same number of children.");
return null;
}
}
}
m_Header = new bool[columnCount];
for (int i = 0; i < columnCount; i++)
m_Header[i] = true;
return m_Header;
}
}
#endif
/// <summary>
/// Method called when a children is added.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of added children.</param>
protected override void OnItemAdded(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
base.OnItemAdded(sender, e);
m_Header = null;
}
/// <summary>
/// Method called when a children is removed.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of removed children.</param>
protected override void OnItemRemoved(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
base.OnItemRemoved(sender, e);
m_Header = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02feae31429679949ab4a4a40c8ad34e
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;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
public partial class DebugUI
{
/// <summary>
/// Generic field - will be serialized in the editor if it's not read-only
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class Field<T> : Widget, IValueField
{
/// <summary>
/// Getter for this field.
/// </summary>
public Func<T> getter { get; set; }
/// <summary>
/// Setter for this field.
/// </summary>
public Action<T> setter { get; set; }
// This should be an `event` but they don't play nice with object initializers in the
// version of C# we use.
/// <summary>
/// Callback used when the value of the field changes.
/// </summary>
public Action<Field<T>, T> onValueChanged;
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
object IValueField.ValidateValue(object value)
{
return ValidateValue((T)value);
}
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
public virtual T ValidateValue(T value)
{
return value;
}
/// <summary>
/// Get the value of the field.
/// </summary>
/// <returns>Value of the field.</returns>
object IValueField.GetValue()
{
return GetValue();
}
/// <summary>
/// Get the value of the field.
/// </summary>
/// <returns>Value of the field.</returns>
public T GetValue()
{
Assert.IsNotNull(getter);
return getter();
}
/// <summary>
/// Set the value of the field.
/// </summary>
/// <param name="value">Input value.</param>
public void SetValue(object value)
{
SetValue((T)value);
}
/// <summary>
/// Set the value of the field.
/// </summary>
/// <param name="value">Input value.</param>
public virtual void SetValue(T value)
{
Assert.IsNotNull(setter);
var v = ValidateValue(value);
if (v == null || !v.Equals(getter()))
{
setter(v);
onValueChanged?.Invoke(this, v);
}
}
}
/// <summary>
/// Boolean field.
/// </summary>
public class BoolField : Field<bool> { }
/// <summary>
/// Boolean field with history.
/// </summary>
public class HistoryBoolField : BoolField
{
/// <summary>
/// History getter for this field.
/// </summary>
public Func<bool>[] historyGetter { get; set; }
/// <summary>
/// Depth of the field's history.
/// </summary>
public int historyDepth => historyGetter?.Length ?? 0;
/// <summary>
/// Get the value of the field at a certain history index.
/// </summary>
/// <param name="historyIndex">Index of the history to query.</param>
/// <returns>Value of the field at the provided history index.</returns>
public bool GetHistoryValue(int historyIndex)
{
Assert.IsNotNull(historyGetter);
Assert.IsTrue(historyIndex >= 0 && historyIndex < historyGetter.Length, "out of range historyIndex");
Assert.IsNotNull(historyGetter[historyIndex]);
return historyGetter[historyIndex]();
}
}
/// <summary>
/// Integer field.
/// </summary>
public class IntField : Field<int>
{
/// <summary>
/// Minimum value function.
/// </summary>
public Func<int> min;
/// <summary>
/// Maximum value function.
/// </summary>
public Func<int> max;
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public int incStep = 1;
/// <summary>
/// Step increment multiplier.
/// </summary>
public int intStepMult = 10;
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
public override int ValidateValue(int value)
{
if (min != null) value = Mathf.Max(value, min());
if (max != null) value = Mathf.Min(value, max());
return value;
}
}
/// <summary>
/// Unsigned integer field.
/// </summary>
public class UIntField : Field<uint>
{
/// <summary>
/// Minimum value function.
/// </summary>
public Func<uint> min;
/// <summary>
/// Maximum value function.
/// </summary>
public Func<uint> max;
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public uint incStep = 1u;
/// <summary>
/// Step increment multiplier.
/// </summary>
public uint intStepMult = 10u;
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
public override uint ValidateValue(uint value)
{
if (min != null) value = (uint)Mathf.Max((int)value, (int)min());
if (max != null) value = (uint)Mathf.Min((int)value, (int)max());
return value;
}
}
/// <summary>
/// Float field.
/// </summary>
public class FloatField : Field<float>
{
/// <summary>
/// Minimum value function.
/// </summary>
public Func<float> min;
/// <summary>
/// Maximum value function.
/// </summary>
public Func<float> max;
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public float incStep = 0.1f;
/// <summary>
/// Step increment multiplier.
/// </summary>
public float incStepMult = 10f;
/// <summary>
/// Number of decimals.
/// </summary>
public int decimals = 3;
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
public override float ValidateValue(float value)
{
if (min != null) value = Mathf.Max(value, min());
if (max != null) value = Mathf.Min(value, max());
return value;
}
}
/// <summary>
/// Generic <see cref="EnumField"/> that stores enumNames and enumValues
/// </summary>
/// <typeparam name="T">The inner type of the field</typeparam>
public abstract class EnumField<T> : Field<T>
{
/// <summary>
/// List of names of the enumerator entries.
/// </summary>
public GUIContent[] enumNames;
private int[] m_EnumValues;
/// <summary>
/// List of values of the enumerator entries.
/// </summary>
public int[] enumValues
{
get => m_EnumValues;
set
{
if (value?.Distinct().Count() != value?.Count())
Debug.LogWarning($"{displayName} - The values of the enum are duplicated, this might lead to a errors displaying the enum");
m_EnumValues = value;
}
}
// Space-delimit PascalCase (https://stackoverflow.com/questions/155303/net-how-can-you-split-a-caps-delimited-string-into-an-array)
static Regex s_NicifyRegEx = new("([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", RegexOptions.Compiled);
/// <summary>
/// Automatically fills the enum names with a given <see cref="Type"/>
/// </summary>
/// <param name="enumType">The enum type</param>
protected void AutoFillFromType(Type enumType)
{
if (enumType == null || !enumType.IsEnum)
throw new ArgumentException($"{nameof(enumType)} must not be null and it must be an Enum type");
using (ListPool<GUIContent>.Get(out var tmpNames))
using (ListPool<int>.Get(out var tmpValues))
{
var enumEntries = enumType.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(fieldInfo => !fieldInfo.IsDefined(typeof(ObsoleteAttribute)) && !fieldInfo.IsDefined(typeof(HideInInspector)));
foreach (var fieldInfo in enumEntries)
{
var description = fieldInfo.GetCustomAttribute<InspectorNameAttribute>();
var displayName = new GUIContent(description == null ? s_NicifyRegEx.Replace(fieldInfo.Name, "$1 ") : description.displayName);
tmpNames.Add(displayName);
tmpValues.Add((int)Enum.Parse(enumType, fieldInfo.Name));
}
enumNames = tmpNames.ToArray();
enumValues = tmpValues.ToArray();
}
}
}
/// <summary>
/// Enumerator field.
/// </summary>
public class EnumField : EnumField<int>
{
internal int[] quickSeparators;
private int[] m_Indexes;
internal int[] indexes => m_Indexes ??= Enumerable.Range(0, enumNames?.Length ?? 0).ToArray();
/// <summary>
/// Get the enumeration value index.
/// </summary>
public Func<int> getIndex { get; set; }
/// <summary>
/// Set the enumeration value index.
/// </summary>
public Action<int> setIndex { get; set; }
/// <summary>
/// Current enumeration value index.
/// </summary>
public int currentIndex
{
get => getIndex();
set => setIndex(value);
}
/// <summary>
/// Generates enumerator values and names automatically based on the provided type.
/// </summary>
public Type autoEnum
{
set
{
AutoFillFromType(value);
InitQuickSeparators();
}
}
internal void InitQuickSeparators()
{
var enumNamesPrefix = enumNames.Select(x =>
{
string[] splitted = x.text.Split('/');
if (splitted.Length == 1)
return "";
else
return splitted[0];
});
quickSeparators = new int[enumNamesPrefix.Distinct().Count()];
string lastPrefix = null;
for (int i = 0, wholeNameIndex = 0; i < quickSeparators.Length; ++i)
{
var currentTestedPrefix = enumNamesPrefix.ElementAt(wholeNameIndex);
while (lastPrefix == currentTestedPrefix)
{
currentTestedPrefix = enumNamesPrefix.ElementAt(++wholeNameIndex);
}
lastPrefix = currentTestedPrefix;
quickSeparators[i] = wholeNameIndex++;
}
}
/// <summary>
/// Set the value of the field.
/// </summary>
/// <param name="value">Input value.</param>
public override void SetValue(int value)
{
Assert.IsNotNull(setter);
var validValue = ValidateValue(value);
// There might be cases that the value does not map the index, look for the correct index
var newCurrentIndex = Array.IndexOf(enumValues, validValue);
if (currentIndex != newCurrentIndex && !validValue.Equals(getter()))
{
setter(validValue);
onValueChanged?.Invoke(this, validValue);
if (newCurrentIndex > -1)
currentIndex = newCurrentIndex;
}
}
}
/// <summary>
/// Object PopupField
/// </summary>
public class ObjectPopupField : Field<Object>
{
/// <summary>
/// Callback to obtain the elemtents of the pop up
/// </summary>
public Func<IEnumerable<Object>> getObjects { get; set; }
}
/// <summary>
/// Enumerator field with history.
/// </summary>
public class HistoryEnumField : EnumField
{
/// <summary>
/// History getter for this field.
/// </summary>
public Func<int>[] historyIndexGetter { get; set; }
/// <summary>
/// Depth of the field's history.
/// </summary>
public int historyDepth => historyIndexGetter?.Length ?? 0;
/// <summary>
/// Get the value of the field at a certain history index.
/// </summary>
/// <param name="historyIndex">Index of the history to query.</param>
/// <returns>Value of the field at the provided history index.</returns>
public int GetHistoryValue(int historyIndex)
{
Assert.IsNotNull(historyIndexGetter);
Assert.IsTrue(historyIndex >= 0 && historyIndex < historyIndexGetter.Length, "out of range historyIndex");
Assert.IsNotNull(historyIndexGetter[historyIndex]);
return historyIndexGetter[historyIndex]();
}
}
/// <summary>
/// Bitfield enumeration field.
/// </summary>
public class BitField : EnumField<Enum>
{
Type m_EnumType;
/// <summary>
/// Generates bitfield values and names automatically based on the provided type.
/// </summary>
public Type enumType
{
get => m_EnumType;
set
{
m_EnumType = value;
AutoFillFromType(value);
}
}
}
/// <summary>
/// Color field.
/// </summary>
public class ColorField : Field<Color>
{
/// <summary>
/// HDR color.
/// </summary>
public bool hdr = false;
/// <summary>
/// Show alpha of the color field.
/// </summary>
public bool showAlpha = true;
// Editor-only
/// <summary>
/// Show the color picker.
/// </summary>
public bool showPicker = true;
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public float incStep = 0.025f;
/// <summary>
/// Step increment multiplier.
/// </summary>
public float incStepMult = 5f;
/// <summary>
/// Number of decimals.
/// </summary>
public int decimals = 3;
/// <summary>
/// Function used to validate the value when updating the field.
/// </summary>
/// <param name="value">Input value.</param>
/// <returns>Validated value.</returns>
public override Color ValidateValue(Color value)
{
if (!hdr)
{
value.r = Mathf.Clamp01(value.r);
value.g = Mathf.Clamp01(value.g);
value.b = Mathf.Clamp01(value.b);
value.a = Mathf.Clamp01(value.a);
}
return value;
}
}
/// <summary>
/// Vector2 field.
/// </summary>
public class Vector2Field : Field<Vector2>
{
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public float incStep = 0.025f;
/// <summary>
/// Step increment multiplier.
/// </summary>
public float incStepMult = 10f;
/// <summary>
/// Number of decimals.
/// </summary>
public int decimals = 3;
}
/// <summary>
/// Vector3 field.
/// </summary>
public class Vector3Field : Field<Vector3>
{
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public float incStep = 0.025f;
/// <summary>
/// Step increment multiplier.
/// </summary>
public float incStepMult = 10f;
/// <summary>
/// Number of decimals.
/// </summary>
public int decimals = 3;
}
/// <summary>
/// Vector4 field.
/// </summary>
public class Vector4Field : Field<Vector4>
{
// Runtime-only
/// <summary>
/// Step increment.
/// </summary>
public float incStep = 0.025f;
/// <summary>
/// Step increment multiplier.
/// </summary>
public float incStepMult = 10f;
/// <summary>
/// Number of decimals.
/// </summary>
public int decimals = 3;
}
/// <summary>
/// Object field.
/// </summary>
public class ObjectField : Field<Object>
{
/// <summary>
/// Object type.
/// </summary>
public Type type = typeof(Object);
}
/// <summary>
/// Object list field.
/// </summary>
public class ObjectListField : Field<Object[]>
{
/// <summary>
/// Objects type.
/// </summary>
public Type type = typeof(Object);
}
/// <summary>
/// Simple message box widget, providing a couple of different styles.
/// </summary>
public class MessageBox : Widget
{
/// <summary>
/// Label style defines text color and background.
/// </summary>
public enum Style
{
/// <summary>
/// Info category
/// </summary>
Info,
/// <summary>
/// Warning category
/// </summary>
Warning,
/// <summary>
/// Error category
/// </summary>
Error
}
/// <summary>
/// Style used to render displayName.
/// </summary>
public Style style = Style.Info;
}
}
}

View File

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

View File

@@ -0,0 +1,136 @@
using System;
namespace UnityEngine.Rendering
{
public partial class DebugUI
{
// Root panel class - we don't want to extend Container here because we need a clear
// separation between debug panels and actual widgets
/// <summary>
/// Root panel class.
/// </summary>
public class Panel : IContainer, IComparable<Panel>
{
/// <summary>
/// Widget flags for this panel.
/// </summary>
public Flags flags { get; set; }
/// <summary>
/// Display name of the panel.
/// </summary>
public string displayName { get; set; }
/// <summary>
/// Group index of the panel.
/// </summary>
public int groupIndex { get; set; }
/// <summary>
/// Path of the panel.
/// </summary>
public string queryPath { get { return displayName; } }
/// <summary>
/// Specify if the panel is editor only.
/// </summary>
public bool isEditorOnly { get { return (flags & Flags.EditorOnly) != 0; } }
/// <summary>
/// Specify if the panel is runtime only.
/// </summary>
public bool isRuntimeOnly { get { return (flags & Flags.RuntimeOnly) != 0; } }
/// <summary>
/// Returns true if the panel is inactive in the editor.
/// </summary>
public bool isInactiveInEditor { get { return (isRuntimeOnly && !Application.isPlaying); } }
/// <summary>
/// Returns true if the panel should always be updated.
/// </summary>
public bool editorForceUpdate { get { return (flags & Flags.EditorForceUpdate) != 0; } }
/// <summary>
/// List of children.
/// </summary>
public ObservableList<Widget> children { get; private set; }
/// <summary>
/// Callback used when the panel is set dirty.
/// </summary>
public event Action<Panel> onSetDirty = delegate { };
/// <summary>
/// Constructor.
/// </summary>
public Panel()
{
children = new ObservableList<Widget>();
children.ItemAdded += OnItemAdded;
children.ItemRemoved += OnItemRemoved;
}
/// <summary>
/// Callback used when a child is added.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of added children.</param>
protected virtual void OnItemAdded(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
if (e.item != null)
{
e.item.panel = this;
e.item.parent = this;
}
SetDirty();
}
/// <summary>
/// Callback used when a child is removed.
/// </summary>
/// <param name="sender">Sender widget.</param>
/// <param name="e">List of removed children.</param>
protected virtual void OnItemRemoved(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e)
{
if (e.item != null)
{
e.item.panel = null;
e.item.parent = null;
}
SetDirty();
}
/// <summary>
/// Set the panel dirty.
/// </summary>
public void SetDirty()
{
int numChildren = children.Count;
for (int i = 0; i < numChildren; i++)
children[i].GenerateQueryPath();
onSetDirty(this);
}
/// <summary>
/// Returns the hash code of the panel.
/// </summary>
/// <returns>Hash code of the panel.</returns>
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + displayName.GetHashCode();
int numChildren = children.Count;
for (int i = 0; i < numChildren; i++)
hash = hash * 23 + children[i].GetHashCode();
return hash;
}
/// <summary>
/// Comparison function.
/// </summary>
/// <param name="other">Panel to compare to.</param>
/// <returns>True if the panels share the same group index.</returns>
int IComparable<Panel>.CompareTo(Panel other) => other == null ? 1 : groupIndex.CompareTo(other.groupIndex);
}
}
}

View File

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

View File

@@ -0,0 +1,337 @@
using System;
using System.Linq;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
/// <summary>
/// Debug UI Class
/// </summary>
public partial class DebugUI
{
/// <summary>
/// Flags for Debug UI widgets.
/// </summary>
[Flags]
public enum Flags
{
/// <summary>
/// None.
/// </summary>
None = 0,
/// <summary>
/// This widget is Editor only.
/// </summary>
EditorOnly = 1 << 1,
/// <summary>
/// This widget is Runtime only.
/// </summary>
RuntimeOnly = 1 << 2,
/// <summary>
/// This widget will force the Debug Editor Window refresh.
/// </summary>
EditorForceUpdate = 1 << 3,
/// <summary>
/// This widget will appear in the section "Frequently Used"
/// </summary>
FrequentlyUsed = 1 << 4
}
/// <summary>
/// Base class for all debug UI widgets.
/// </summary>
public abstract class Widget
{
// Set to null until it's added to a panel, be careful
/// <summary>
/// Panels containing the widget.
/// </summary>
protected Panel m_Panel;
/// <summary>
/// Panels containing the widget.
/// </summary>
public virtual Panel panel
{
get { return m_Panel; }
internal set { m_Panel = value; }
}
/// <summary>
/// Parent container.
/// </summary>
protected IContainer m_Parent;
/// <summary>
/// Parent container.
/// </summary>
public virtual IContainer parent
{
get { return m_Parent; }
internal set { m_Parent = value; }
}
/// <summary>
/// Flags for the widget.
/// </summary>
public Flags flags { get; set; }
/// <summary>
/// Display name.
/// </summary>
public string displayName { get; set; }
/// <summary>
/// Tooltip.
/// </summary>
public string tooltip { get; set; }
/// <summary>
/// Path of the widget.
/// </summary>
public string queryPath { get; private set; }
/// <summary>
/// True if the widget is Editor only.
/// </summary>
public bool isEditorOnly => flags.HasFlag(Flags.EditorOnly);
/// <summary>
/// True if the widget is Runtime only.
/// </summary>
public bool isRuntimeOnly => flags.HasFlag(Flags.RuntimeOnly);
/// <summary>
/// True if the widget is inactive in the editor (i.e. widget is runtime only and the application is not 'Playing').
/// </summary>
public bool isInactiveInEditor => (isRuntimeOnly && !Application.isPlaying);
/// <summary>
/// Optional delegate that can be used to conditionally hide widgets at runtime (e.g. due to state of other widgets).
/// </summary>
public Func<bool> isHiddenCallback;
/// <summary>
/// If <see cref="isHiddenCallback">shouldHideDelegate</see> has been set and returns true, the widget is hidden from the UI.
/// </summary>
public bool isHidden => isHiddenCallback?.Invoke() ?? false;
internal virtual void GenerateQueryPath()
{
queryPath = displayName.Trim();
if (m_Parent != null)
queryPath = m_Parent.queryPath + " -> " + queryPath;
}
/// <summary>
/// Returns the hash code of the widget.
/// </summary>
/// <returns>The hash code of the widget.</returns>
public override int GetHashCode()
{
return queryPath.GetHashCode() ^ isHidden.GetHashCode();
}
/// <summary>
/// Helper struct to allow more compact initialization of widgets.
/// </summary>
public struct NameAndTooltip
{
/// <summary>
/// The name
/// </summary>
public string name;
/// <summary>
/// The tooltip
/// </summary>
public string tooltip;
}
/// <summary>
/// Helper setter to allow more compact initialization of widgets.
/// </summary>
public NameAndTooltip nameAndTooltip
{
set
{
displayName = value.name;
tooltip = value.tooltip;
}
}
}
/// <summary>
/// Interface for widgets that can contain other widgets.
/// </summary>
public interface IContainer
{
/// <summary>
/// List of children of the container.
/// </summary>
ObservableList<Widget> children { get; }
/// <summary>
/// Display name of the container.
/// </summary>
string displayName { get; set; }
/// <summary>
/// Path of the container.
/// </summary>
string queryPath { get; }
}
/// <summary>
/// Any widget that implements this will be considered for serialization (only if the setter is set and thus is not read-only)
/// </summary>
public interface IValueField
{
/// <summary>
/// Return the value of the field.
/// </summary>
/// <returns>Value of the field.</returns>
object GetValue();
/// <summary>
/// Set the value of the field.
/// </summary>
/// <param name="value">Input value.</param>
void SetValue(object value);
/// <summary>
/// Function used to validate the value when setting it.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
object ValidateValue(object value);
}
// Miscellaneous
/// <summary>
/// Button widget.
/// </summary>
public class Button : Widget
{
/// <summary>
/// Action performed by the button.
/// </summary>
public Action action { get; set; }
}
/// <summary>
/// Read only Value widget.
/// </summary>
public class Value : Widget
{
/// <summary>
/// Getter for the Value.
/// </summary>
public Func<object> getter { get; set; }
/// <summary>
/// Refresh rate for the read-only value (runtime only)
/// </summary>
public float refreshRate = 0.1f;
/// <summary>
/// Optional C# numeric format string, using following syntax: "{0[:numericFormatString]}"
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
/// and https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting
/// Example: 123.45678 with formatString "{0:F2} ms" --> "123.45 ms".
/// </summary>
public string formatString = null;
/// <summary>
/// Constructor.
/// </summary>
public Value()
{
displayName = "";
}
/// <summary>
/// Returns the value of the widget.
/// </summary>
/// <returns>The value of the widget.</returns>
public virtual object GetValue()
{
Assert.IsNotNull(getter);
return getter();
}
/// <summary>
/// Returns the formatted value string for display purposes.
/// </summary>
/// <param name="value">Value to be formatted.</param>
/// <returns>The formatted value string.</returns>
public virtual string FormatString(object value)
{
return string.IsNullOrEmpty(formatString) ? $"{value}" : string.Format(formatString, value);
}
}
/// <summary>
/// Progress bar value.
/// </summary>
public class ProgressBarValue : Value
{
/// <summary>
/// Minimum value.
/// </summary>
public float min = 0f;
/// <summary>
/// Maximum value.
/// </summary>
public float max = 1f;
/// <summary>
/// Get the current progress string, remapped to [0, 1] range, representing the progress between min and max.
/// </summary>
/// <param name="value">Value to be formatted.</param>
/// <returns>Formatted progress percentage string between 0% and 100%.</returns>
public override string FormatString(object value)
{
static float Remap01(float v, float x0, float y0) => (v - x0) / (y0 - x0);
float clamped = Mathf.Clamp((float)value, min, max);
float percentage = Remap01(clamped, min, max);
return $"{percentage:P1}";
}
}
/// <summary>
/// Tuple of Value widgets for creating tabular UI.
/// </summary>
public class ValueTuple : Widget
{
/// <summary>
/// Number of elements in the tuple.
/// </summary>
public int numElements
{
get
{
Assert.IsTrue(values.Length > 0);
return values.Length;
}
}
/// <summary>
/// Value widgets.
/// </summary>
public Value[] values;
/// <summary>
/// Refresh rate for the read-only values (runtime only)
/// </summary>
public float refreshRate => values.FirstOrDefault()?.refreshRate ?? 0.1f;
/// <summary>
/// The currently pinned element index, or -1 if none are pinned.
/// </summary>
public int pinnedElementIndex = -1;
}
}
}

View File

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

View File

@@ -0,0 +1,213 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.UI;
using UnityEngine.InputSystem.EnhancedTouch;
#endif
using System;
using System.Collections;
using System.Diagnostics;
using UnityEngine.EventSystems;
namespace UnityEngine.Rendering
{
class DebugUpdater : MonoBehaviour
{
static DebugUpdater s_Instance = null;
ScreenOrientation m_Orientation;
bool m_RuntimeUiWasVisibleLastFrame = false;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void RuntimeInit()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (DebugManager.instance.enableRuntimeUI)
EnableRuntime();
#endif
}
internal static void SetEnabled(bool enabled)
{
if (enabled)
EnableRuntime();
else
DisableRuntime();
}
static void EnableRuntime()
{
if (s_Instance != null)
return;
var go = new GameObject { name = "[Debug Updater]" };
s_Instance = go.AddComponent<DebugUpdater>();
s_Instance.m_Orientation = Screen.orientation;
DontDestroyOnLoad(go);
DebugManager.instance.EnableInputActions();
#if USE_INPUT_SYSTEM
EnhancedTouchSupport.Enable();
#endif
}
static void DisableRuntime()
{
DebugManager debugManager = DebugManager.instance;
debugManager.displayRuntimeUI = false;
debugManager.displayPersistentRuntimeUI = false;
if (s_Instance != null)
{
CoreUtils.Destroy(s_Instance.gameObject);
s_Instance = null;
}
}
internal static void HandleInternalEventSystemComponents(bool uiEnabled)
{
if (s_Instance == null)
return;
if (uiEnabled)
s_Instance.EnsureExactlyOneEventSystem();
else
s_Instance.DestroyDebugEventSystem();
}
void EnsureExactlyOneEventSystem()
{
var eventSystems = FindObjectsOfType<EventSystem>();
var debugEventSystem = GetComponent<EventSystem>();
if (eventSystems.Length > 1 && debugEventSystem != null)
{
Debug.Log($"More than one EventSystem detected in scene. Destroying EventSystem owned by DebugUpdater.");
DestroyDebugEventSystem();
}
else if (eventSystems.Length == 0)
{
Debug.Log($"No EventSystem available. Creating a new EventSystem to enable Rendering Debugger runtime UI.");
CreateDebugEventSystem();
}
else
{
StartCoroutine(DoAfterInputModuleUpdated(CheckInputModuleExists));
}
}
IEnumerator DoAfterInputModuleUpdated(Action action)
{
// EventSystem.current.currentInputModule is not updated immediately when EventSystem.current changes. It happens
// with a delay in EventSystem.Update(), so wait a couple of frames to ensure that has happened.
yield return new WaitForEndOfFrame();
yield return new WaitForEndOfFrame();
action.Invoke();
}
void CheckInputModuleExists()
{
if (EventSystem.current != null && EventSystem.current.currentInputModule == null)
{
Debug.LogWarning("Found a game object with EventSystem component but no corresponding BaseInputModule component - Debug UI input might not work correctly.");
}
}
#if USE_INPUT_SYSTEM
void AssignDefaultActions()
{
if (EventSystem.current != null && EventSystem.current.currentInputModule is InputSystemUIInputModule inputSystemModule)
{
// FIXME: In order to activate default input actions in player builds (required for touch input to work),
// we need to call InputSystemUIInputModule.AssignDefaultActions() which was added in com.unity.inputsystem@1.1.0-pre.5.
// However, there is a problem in InputSystem package version ordering, where it sorts this version as an
// older version than it should be. Hence we cannot write a version define to conditionally compile this function call.
// Instead, we use reflection to see if the function is there and can be invoked.
//
// Once com.unity.inputsystem@1.1.0 is available, create an INPUTSYSTEM_1_1_0_OR_GREATER version define and use it
// to conditionally call AssignDefaultActions().
System.Reflection.MethodInfo assignDefaultActionsMethod = inputSystemModule.GetType().GetMethod("AssignDefaultActions");
if (assignDefaultActionsMethod != null)
{
assignDefaultActionsMethod.Invoke(inputSystemModule, null);
}
}
CheckInputModuleExists();
}
#endif
void CreateDebugEventSystem()
{
gameObject.AddComponent<EventSystem>();
#if USE_INPUT_SYSTEM
gameObject.AddComponent<InputSystemUIInputModule>();
StartCoroutine(DoAfterInputModuleUpdated(AssignDefaultActions));
#else
gameObject.AddComponent<StandaloneInputModule>();
#endif
}
void DestroyDebugEventSystem()
{
var eventSystem = GetComponent<EventSystem>();
#if USE_INPUT_SYSTEM
var inputModule = GetComponent<InputSystemUIInputModule>();
if (inputModule)
{
CoreUtils.Destroy(inputModule);
StartCoroutine(DoAfterInputModuleUpdated(AssignDefaultActions));
}
#else
CoreUtils.Destroy(GetComponent<StandaloneInputModule>());
CoreUtils.Destroy(GetComponent<BaseInput>());
#endif
CoreUtils.Destroy(eventSystem);
}
void Update()
{
DebugManager debugManager = DebugManager.instance;
// Runtime UI visibility can change i.e. due to scene unload - allow component cleanup in this case.
if (m_RuntimeUiWasVisibleLastFrame != debugManager.displayRuntimeUI)
{
HandleInternalEventSystemComponents(debugManager.displayRuntimeUI);
}
debugManager.UpdateActions();
if (debugManager.GetAction(DebugAction.EnableDebugMenu) != 0.0f ||
debugManager.GetActionToggleDebugMenuWithTouch())
{
debugManager.displayRuntimeUI = !debugManager.displayRuntimeUI;
}
if (debugManager.displayRuntimeUI)
{
if (debugManager.GetAction(DebugAction.ResetAll) != 0.0f)
debugManager.Reset();
if (debugManager.GetActionReleaseScrollTarget())
debugManager.SetScrollTarget(null); // Allow mouse wheel scroll without causing auto-scroll
}
if (m_Orientation != Screen.orientation)
{
StartCoroutine(RefreshRuntimeUINextFrame());
m_Orientation = Screen.orientation;
}
m_RuntimeUiWasVisibleLastFrame = debugManager.displayRuntimeUI;
}
static IEnumerator RefreshRuntimeUINextFrame()
{
yield return null; // Defer runtime UI refresh to next frame to allow canvas to update first.
DebugManager.instance.ReDrawOnScreenDebug();
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0f240cc177d08124a9694021c19c0dc4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Represents a system bottleneck, meaning the factor that is most dominant in determining
/// the total frame time.
/// </summary>
internal enum PerformanceBottleneck
{
Indeterminate, // Cannot be determined
PresentLimited, // Limited by presentation (vsync or framerate cap)
CPU, // Limited by CPU (main and/or render thread)
GPU, // Limited by GPU
Balanced, // Limited by both CPU and GPU, i.e. well balanced
}
/// <summary>
/// BottleneckHistogram represents the distribution of bottlenecks over the Bottleneck History Window,
/// the size of which is determined by <see cref="DebugFrameTiming.bottleneckHistorySize"/>.
/// </summary>
internal struct BottleneckHistogram
{
internal float PresentLimited;
internal float CPU;
internal float GPU;
internal float Balanced;
};
/// <summary>
/// Container class for bottleneck history with helper to calculate histogram.
/// </summary>
internal class BottleneckHistory
{
public BottleneckHistory(int initialCapacity)
{
m_Bottlenecks.Capacity = initialCapacity;
}
List<PerformanceBottleneck> m_Bottlenecks = new();
internal BottleneckHistogram Histogram;
internal void DiscardOldSamples(int historySize)
{
Debug.Assert(historySize > 0, "Invalid sampleHistorySize");
while (m_Bottlenecks.Count >= historySize)
m_Bottlenecks.RemoveAt(0);
m_Bottlenecks.Capacity = historySize;
}
internal void AddBottleneckFromAveragedSample(FrameTimeSample frameHistorySampleAverage)
{
var bottleneck = DetermineBottleneck(frameHistorySampleAverage);
m_Bottlenecks.Add(bottleneck);
}
internal void ComputeHistogram()
{
var stats = new BottleneckHistogram();
for (int i = 0; i < m_Bottlenecks.Count; i++)
{
switch (m_Bottlenecks[i])
{
case PerformanceBottleneck.Balanced:
stats.Balanced++;
break;
case PerformanceBottleneck.CPU:
stats.CPU++;
break;
case PerformanceBottleneck.GPU:
stats.GPU++;
break;
case PerformanceBottleneck.PresentLimited:
stats.PresentLimited++;
break;
}
}
stats.Balanced /= m_Bottlenecks.Count;
stats.CPU /= m_Bottlenecks.Count;
stats.GPU /= m_Bottlenecks.Count;
stats.PresentLimited /= m_Bottlenecks.Count;
Histogram = stats;
}
static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s)
{
const float kNearFullFrameTimeThresholdPercent = 0.2f;
const float kNonZeroPresentWaitTimeMs = 0.5f;
if (s.GPUFrameTime == 0 || s.MainThreadCPUFrameTime == 0) // In direct mode, render thread doesn't exist
return PerformanceBottleneck.Indeterminate; // Missing data
float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime;
// GPU time is close to frame time, CPU times are not
if (s.GPUFrameTime > fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.GPU;
// One of the CPU times is close to frame time, GPU is not
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
(s.MainThreadCPUFrameTime > fullFrameTimeWithMargin ||
s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin))
return PerformanceBottleneck.CPU;
// Main thread waited due to Vsync or target frame rate
if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs)
{
// None of the times are close to frame time
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.PresentLimited;
}
return PerformanceBottleneck.Balanced;
}
internal void Clear()
{
m_Bottlenecks.Clear();
Histogram = new BottleneckHistogram();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 331aa10d229f4c2fb9a9240812fb45d6
timeCreated: 1629185832

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Represents timing data captured from a single frame.
/// </summary>
internal struct FrameTimeSample
{
internal float FramesPerSecond;
internal float FullFrameTime;
internal float MainThreadCPUFrameTime;
internal float MainThreadCPUPresentWaitTime;
internal float RenderThreadCPUFrameTime;
internal float GPUFrameTime;
internal FrameTimeSample(float initValue)
{
FramesPerSecond = initValue;
FullFrameTime = initValue;
MainThreadCPUFrameTime = initValue;
MainThreadCPUPresentWaitTime = initValue;
RenderThreadCPUFrameTime = initValue;
GPUFrameTime = initValue;
}
};
/// <summary>
/// Container class for sample history with helpers to calculate min, max and average in one pass.
/// </summary>
class FrameTimeSampleHistory
{
public FrameTimeSampleHistory(int initialCapacity)
{
m_Samples.Capacity = initialCapacity;
}
List<FrameTimeSample> m_Samples = new();
internal FrameTimeSample SampleAverage;
internal FrameTimeSample SampleMin;
internal FrameTimeSample SampleMax;
internal void Add(FrameTimeSample sample)
{
m_Samples.Add(sample);
}
// Helper functions
static Func<float, float, float> s_SampleValueAdd = (float value, float other) =>
{
return value + other;
};
static Func<float, float, float> s_SampleValueMin = (float value, float other) =>
{
return other > 0 ? Mathf.Min(value, other) : value;
};
static Func<float, float, float> s_SampleValueMax = (float value, float other) =>
{
return Mathf.Max(value, other);
};
static Func<float, float, float> s_SampleValueCountValid = (float value, float other) =>
{
return other > 0 ? value + 1 : value;
};
static Func<float, float, float> s_SampleValueEnsureValid = (float value, float other) =>
{
return other > 0 ? value : 0;
};
static Func<float, float, float> s_SampleValueDivide = (float value, float other) =>
{
return other > 0 ? value / other : 0;
};
internal void ComputeAggregateValues()
{
void ForEachSampleMember(ref FrameTimeSample aggregate, FrameTimeSample sample, Func<float, float, float> func)
{
aggregate.FramesPerSecond = func(aggregate.FramesPerSecond, sample.FramesPerSecond);
aggregate.FullFrameTime = func(aggregate.FullFrameTime, sample.FullFrameTime);
aggregate.MainThreadCPUFrameTime = func(aggregate.MainThreadCPUFrameTime, sample.MainThreadCPUFrameTime);
aggregate.MainThreadCPUPresentWaitTime = func(aggregate.MainThreadCPUPresentWaitTime, sample.MainThreadCPUPresentWaitTime);
aggregate.RenderThreadCPUFrameTime = func(aggregate.RenderThreadCPUFrameTime, sample.RenderThreadCPUFrameTime);
aggregate.GPUFrameTime = func(aggregate.GPUFrameTime, sample.GPUFrameTime);
};
FrameTimeSample average = new();
FrameTimeSample min = new(float.MaxValue);
FrameTimeSample max = new(float.MinValue);
FrameTimeSample numValidSamples = new(); // Using the struct to record how many valid samples each field has
for (int i = 0; i < m_Samples.Count; i++)
{
var s = m_Samples[i];
ForEachSampleMember(ref min, s, s_SampleValueMin);
ForEachSampleMember(ref max, s, s_SampleValueMax);
ForEachSampleMember(ref average, s, s_SampleValueAdd);
ForEachSampleMember(ref numValidSamples, s, s_SampleValueCountValid);
}
ForEachSampleMember(ref min, numValidSamples, s_SampleValueEnsureValid);
ForEachSampleMember(ref max, numValidSamples, s_SampleValueEnsureValid);
ForEachSampleMember(ref average, numValidSamples, s_SampleValueDivide);
SampleAverage = average;
SampleMin = min;
SampleMax = max;
}
internal void DiscardOldSamples(int sampleHistorySize)
{
Debug.Assert(sampleHistorySize > 0, "Invalid sampleHistorySize");
while (m_Samples.Count >= sampleHistorySize)
m_Samples.RemoveAt(0);
m_Samples.Capacity = sampleHistorySize;
}
internal void Clear()
{
m_Samples.Clear();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d7cc2267acce47fdae2ece838f34df48
timeCreated: 1629185717

View File

@@ -0,0 +1,22 @@
using System;
using UnityEngine;
namespace UnityEngine.Rendering
{
/// <summary>
/// Interface for storing the debug settings
/// </summary>
public interface IDebugDisplaySettings : IDebugDisplaySettingsQuery
{
/// <summary>
/// Reset the stored debug settings
/// </summary>
void Reset();
/// <summary>
/// Executes an action for each element
/// </summary>
/// <param name="onExecute"></param>
void ForEach(Action<IDebugDisplaySettingsData> onExecute);
}
}

View File

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

View File

@@ -0,0 +1,14 @@
namespace UnityEngine.Rendering
{
/// <summary>
/// Debug UI panel interface
/// </summary>
public interface IDebugDisplaySettingsData : IDebugDisplaySettingsQuery
{
/// <summary>
/// Creates the debug UI panel needed for these debug settings.
/// </summary>
/// <returns>The debug UI panel created.</returns>
IDebugDisplaySettingsPanelDisposable CreatePanel();
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Debug UI panel
/// </summary>
public interface IDebugDisplaySettingsPanel
{
/// <summary>
/// The name used when displaying this panel.
/// </summary>
string PanelName { get; }
/// <summary>
/// Widgets used by this panel.
/// </summary>
DebugUI.Widget[] Widgets { get; }
/// <summary>
/// Flags to be applied to the top-level panel.
/// </summary>
DebugUI.Flags Flags { get; }
}
/// <summary>
/// Debug UI panel disposable
/// </summary>
public interface IDebugDisplaySettingsPanelDisposable : IDebugDisplaySettingsPanel, IDisposable
{
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using UnityEngine;
namespace UnityEngine.Rendering
{
/// <summary>
/// Interface for determining what kind of debug settings are currently active.
/// </summary>
public interface IDebugDisplaySettingsQuery
{
/// <summary>
/// Checks whether ANY of the debug settings are currently active.
/// </summary>
bool AreAnySettingsActive { get; }
/// <summary>
/// Checks whether the current state of these settings allows post-processing.
/// </summary>
bool IsPostProcessingAllowed { get; }
/// <summary>
/// Checks whether lighting is active for these settings.
/// </summary>
bool IsLightingActive { get; }
/// <summary>
/// Attempts to get the color used to clear the screen for this debug setting.
/// </summary>
/// <param name="color">A reference to the screen clear color to use.</param>
/// <returns>"true" if we updated the color, "false" if we didn't change anything.</returns>
bool TryGetScreenClearColor(ref Color color);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 52acb35497424fdcb9cc2535113a9c15
timeCreated: 1613054809

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Volume debug settings.
/// This variant is obsolete and kept only for not breaking user code. Use <see cref="IVolumeDebugSettings2"/> for all new usage.
/// </summary>
[Obsolete("This variant is obsolete and kept only for not breaking user code. Use IVolumeDebugSettings2 for all new usage.", false)]
public interface IVolumeDebugSettings
{
/// <summary>Selected component.</summary>
int selectedComponent { get; set; }
/// <summary>Current camera to debug.</summary>
Camera selectedCamera { get; }
/// <summary>Returns the collection of registered cameras.</summary>
IEnumerable<Camera> cameras { get; }
/// <summary>Selected camera index.</summary>
int selectedCameraIndex { get; set; }
/// <summary>Selected camera volume stack.</summary>
VolumeStack selectedCameraVolumeStack { get; }
/// <summary>Selected camera volume layer mask.</summary>
LayerMask selectedCameraLayerMask { get; }
/// <summary>Selected camera volume position.</summary>
Vector3 selectedCameraPosition { get; }
/// <summary>Type of the current component to debug.</summary>
Type selectedComponentType { get; set; }
/// <summary>
/// Obtains the Volumes
/// </summary>
/// <returns>The list of <see cref="Volume"/></returns>
Volume[] GetVolumes();
/// <summary>
/// Return if the <see cref="Volume"/> has influence
/// </summary>
/// <param name="volume"><see cref="Volume"/> to check the influence</param>
/// <returns>If the volume has influence</returns>
bool VolumeHasInfluence(Volume volume);
/// <summary>
/// Refreshes the volumes, fetches the stored volumes on the panel
/// </summary>
/// <param name="newVolumes">The list of <see cref="Volume"/> to refresh</param>
/// <returns>If the volumes have been refreshed</returns>
bool RefreshVolumes(Volume[] newVolumes);
/// <summary>
/// Obtains the volume weight
/// </summary>
/// <param name="volume"><see cref="Volume"/></param>
/// <returns>The weight of the volume</returns>
float GetVolumeWeight(Volume volume);
}
/// <summary>
/// Volume debug settings.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public interface IVolumeDebugSettings2 : IVolumeDebugSettings
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Specifies the render pipelie
/// </summary>
Type targetRenderPipeline { get; }
/// <summary>List of Volume component types and their path</summary>
List<(string, Type)> volumeComponentsPathAndType { get; }
}
}

View File

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

View File

@@ -0,0 +1,205 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
#endif
using UnityEditor;
namespace UnityEngine.Rendering
{
/// <summary>
/// Provides mouse position for debugging purpose.
/// </summary>
public class MousePositionDebug
{
// Singleton
private static MousePositionDebug s_Instance = null;
/// <summary>
/// Singleton instance.
/// </summary>
static public MousePositionDebug instance
{
get
{
if (s_Instance == null)
{
s_Instance = new MousePositionDebug();
}
return s_Instance;
}
}
#if UNITY_EDITOR
[ExecuteAlways]
class GameViewEventCatcher : MonoBehaviour
{
public static GameViewEventCatcher s_Instance = null;
public static void Cleanup()
{
if (s_Instance != null)
{
// Either we call DestroyImmediate or Destroy we get an error :(
// GameViewEventCatcher is only use for SSR debugging currently so comment this code and uncomment it if you want to debug SSR
//DestroyImmediate(s_Instance.gameObject);
//Destroy(s_Instance.gameObject);
}
}
public static void Build()
{
Cleanup();
var go = new GameObject("__GameViewEventCatcher");
go.hideFlags = HideFlags.HideAndDontSave;
s_Instance = go.AddComponent<GameViewEventCatcher>();
}
void Update()
{
Vector2 mousePosition;
bool rightClickPressed = false;
bool endKeyPressed = false;
#if USE_INPUT_SYSTEM
mousePosition = Pointer.current != null ? Pointer.current.position.ReadValue() : new Vector2(-1, -1);
if (Mouse.current != null)
rightClickPressed = Mouse.current.rightButton.isPressed;
if (Keyboard.current != null)
endKeyPressed = Keyboard.current.endKey.isPressed;
#else
mousePosition = Input.mousePosition;
rightClickPressed = Input.GetMouseButton(1);
endKeyPressed = Input.GetKey(KeyCode.End);
#endif
if (mousePosition.x < 0
|| mousePosition.y < 0
|| mousePosition.x > Screen.width
|| mousePosition.y > Screen.height)
return;
instance.m_mousePosition = mousePosition;
instance.m_mousePosition.y = Screen.height - instance.m_mousePosition.y;
if (rightClickPressed)
instance.m_MouseClickPosition = instance.m_mousePosition;
if (endKeyPressed)
instance.m_MouseClickPosition = instance.m_mousePosition;
}
}
private Vector2 m_mousePosition = Vector2.zero;
Vector2 m_MouseClickPosition = Vector2.zero;
private void OnSceneGUI(UnityEditor.SceneView sceneview)
{
m_mousePosition = Event.current.mousePosition;
switch (Event.current.type)
{
case EventType.MouseDown:
m_MouseClickPosition = m_mousePosition;
break;
case EventType.KeyDown:
switch (Event.current.keyCode)
{
case KeyCode.End:
// Usefull we you don't want to change the scene viewport but still update the mouse click position
m_MouseClickPosition = m_mousePosition;
sceneview.Repaint();
break;
}
break;
}
}
#endif
/// <summary>
/// Initialize the MousePositionDebug class.
/// </summary>
public void Build()
{
#if UNITY_EDITOR
UnityEditor.SceneView.duringSceneGui -= OnSceneGUI;
UnityEditor.SceneView.duringSceneGui += OnSceneGUI;
// Disabled as it cause error: GameViewEventCatcher is only use for SSR debugging currently so comment this code and uncomment it if you want to debug SSR
//GameViewEventCatcher.Build();
#endif
}
/// <summary>
/// Cleanup the MousePositionDebug class.
/// </summary>
public void Cleanup()
{
#if UNITY_EDITOR
UnityEditor.SceneView.duringSceneGui -= OnSceneGUI;
// Disabled as it cause error: GameViewEventCatcher is only use for SSR debugging currently so comment this code and uncomment it if you want to debug SSR
//GameViewEventCatcher.Cleanup();
#endif
}
/// <summary>
/// Get the mouse position in the scene or game view.
/// </summary>
/// <param name="ScreenHeight">Window height.</param>
/// <param name="sceneView">Get position in the scene view?</param>
/// <returns>Coordinates of the mouse in the specified window.</returns>
public Vector2 GetMousePosition(float ScreenHeight, bool sceneView)
{
#if UNITY_EDITOR
if (sceneView)
{
// In play mode, m_mousePosition the one in the scene view
Vector2 mousePixelCoord = EditorGUIUtility.PointsToPixels(m_mousePosition);
mousePixelCoord.y = (ScreenHeight - 1.0f) - mousePixelCoord.y;
return mousePixelCoord;
}
else
{
// In play mode, Input.mousecoords matches the position in the game view
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
return GetInputMousePosition();
}
else
{
// In non-play mode, only m_mousePosition is valid.
// We force -1, -1 as a game view pixel pos to avoid
// rendering un-wanted effects
return new Vector2(-1.0f, -1.0f);
}
}
#else
// In app mode, we only use the Input.mousecoords
return GetInputMousePosition();
#endif
}
Vector2 GetInputMousePosition()
{
#if USE_INPUT_SYSTEM
return Pointer.current != null ? Pointer.current.position.ReadValue() : new Vector2(-1, -1);
#else
return Input.mousePosition;
#endif
}
/// <summary>
/// Returns the position of the mouse click.
/// </summary>
/// <param name="ScreenHeight">Window height.</param>
/// <returns>The coordinates of the mouse click.</returns>
public Vector2 GetMouseClickPosition(float ScreenHeight)
{
#if UNITY_EDITOR
Vector2 mousePixelCoord = EditorGUIUtility.PointsToPixels(m_MouseClickPosition);
mousePixelCoord.y = (ScreenHeight - 1.0f) - mousePixelCoord.y;
return mousePixelCoord;
#else
return Vector2.zero;
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d2160913896effa4aa65b93012ff3465
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a11be56b627f5034bbca389c76bd3964
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
fileFormatVersion: 2
guid: 74a5091d8707f334b9a5c31ef71a64ba
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontName: Perfect DOS VGA 437
fontNames:
- Perfect DOS VGA 437
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 2
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3774a3e29f7c59445ba79c15769126fd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,196 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1153602445894428
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 224711363741255626}
- component: {fileID: 223912878945851142}
- component: {fileID: 114908889885781782}
- component: {fileID: 114649910605725082}
- component: {fileID: 114530362809716058}
m_Layer: 5
m_Name: DebugUICanvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &224711363741255626
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1153602445894428}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &223912878945851142
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1153602445894428}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 1
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 32767
m_TargetDisplay: 0
--- !u!114 &114908889885781782
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1153602445894428}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 0
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 800, y: 600}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &114649910605725082
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1153602445894428}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &114530362809716058
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1153602445894428}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 76db615e524a19c4990482d75a475543, type: 3}
m_Name:
m_EditorClassIdentifier:
panelPrefab: {fileID: 224481716535368988, guid: daa46a58178a6ad41ae1ddc2dc7f856d, type: 3}
prefabs:
- type: UnityEngine.Rendering.DebugUI+Value, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: dc0f88987826e6e48b1fe9c7c2b53a53, type: 3}
- type: UnityEngine.Rendering.DebugUI+BoolField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224131888606727344, guid: ce347ad101f41ee4ab5c3fbc0ea447db, type: 3}
- type: UnityEngine.Rendering.DebugUI+IntField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: ae00bb75e0cd5b04b8fe7fb4ab662629, type: 3}
- type: UnityEngine.Rendering.DebugUI+UIntField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: f22bcc84a5f4a1944b075a2c4ac71493, type: 3}
- type: UnityEngine.Rendering.DebugUI+FloatField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: d8c744701b43c864b88e7f8144e19bc5, type: 3}
- type: UnityEngine.Rendering.DebugUI+EnumField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224224135738715566, guid: 988db55689193434fb0b3b89538f978f, type: 3}
- type: UnityEngine.Rendering.DebugUI+Button, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224438017010656346, guid: f6ce33b91f6ffe54cadacbf4bb112440, type: 3}
- type: UnityEngine.Rendering.DebugUI+Foldout, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224053494956566916, guid: 1c87ab2ce8b8b304d98fbe9a734b1f74, type: 3}
- type: UnityEngine.Rendering.DebugUI+ColorField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224636372931965878, guid: 77c185820dd1a464eac89cae3abccddf, type: 3}
- type: UnityEngine.Rendering.DebugUI+Vector2Field, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224169904409585018, guid: 326f7c58aed965d41bf7805a782d1e44, type: 3}
- type: UnityEngine.Rendering.DebugUI+Vector3Field, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224119945032119512, guid: 94afea5f242d72547979595ba963f335, type: 3}
- type: UnityEngine.Rendering.DebugUI+Vector4Field, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224325631027038092, guid: d47f009476100f545971a81ede14c750, type: 3}
- type: UnityEngine.Rendering.DebugUI+VBox, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224489511352681190, guid: ca3e294656861a64b8aeeb9f916da0a9, type: 3}
- type: UnityEngine.Rendering.DebugUI+HBox, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224719784157228276, guid: f7f5e36797cf0c1408561665c67b179b, type: 3}
- type: UnityEngine.Rendering.DebugUI+Container, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224284813447651300, guid: 38a07789c9e87004dad98c2909f58369, type: 3}
- type: UnityEngine.Rendering.DebugUI+BitField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 5833802642077810669, guid: 7c78b588b2e1f7c4a86ca4a985cf6e4a, type: 3}
- type: UnityEngine.Rendering.DebugUI+HistoryBoolField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 108402283379224504, guid: 5088d0220f0c4df439cf06c5c270eacb, type: 3}
- type: UnityEngine.Rendering.DebugUI+HistoryEnumField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 8535926254376877601, guid: b2da6b27df236b144b3516ed8e7d36ac, type: 3}
- type: UnityEngine.Rendering.DebugUI+Table, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224284813447651300, guid: 38a07789c9e87004dad98c2909f58369, type: 3}
- type: UnityEngine.Rendering.DebugUI+Table+Row, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224053494956566916, guid: 2d019437ff89b8d44949727731cd9357, type: 3}
- type: UnityEngine.Rendering.DebugUI+MessageBox, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224053494956566916, guid: 10a25524b0986f9488b430e2829bbbe8, type: 3}
- type: UnityEngine.Rendering.DebugUI+ProgressBarValue, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: d3770aaa3bbd8384aabab9ddd383e21e, type: 3}
- type: UnityEngine.Rendering.DebugUI+ValueTuple, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: a2148203dd960814ca5db0c293ceda35, type: 3}
- type: UnityEngine.Rendering.DebugUI+ObjectField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224720214277421396, guid: a87cfaef648f17c41b568e842e068c51, type: 3}
- type: UnityEngine.Rendering.DebugUI+ObjectListField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 224224135738715566, guid: ad83bc56407925d44a77a5bd01cd6783, type: 3}
- type: UnityEngine.Rendering.DebugUI+ObjectPopupField, Unity.RenderPipelines.Core.Runtime,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
prefab: {fileID: 4224455051203994714, guid: 48fb043c850cadc4a883b53785e14c6e, type: 3}

Some files were not shown because too many files have changed in this diff Show More