using System.Collections.Generic; using Unity.Collections.LowLevel.Unsafe; namespace UnityEngine.Rendering { /// /// Constant Buffer management class. /// public class ConstantBuffer { static List m_RegisteredConstantBuffers = new List(); /// /// Update the GPU data of the constant buffer and bind it globally. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. /// Shader porperty id to bind the constant buffer to. public static void PushGlobal(CommandBuffer cmd, in CBType data, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.UpdateData(cmd, data); cb.SetGlobal(cmd, shaderId); } /// /// Update the GPU data of the constant buffer and bind it to a compute shader. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. /// Compute shader to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. public static void Push(CommandBuffer cmd, in CBType data, ComputeShader cs, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.UpdateData(cmd, data); cb.Set(cmd, cs, shaderId); } /// /// Update the GPU data of the constant buffer and bind it to a material. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. /// Material to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. public static void Push(CommandBuffer cmd, in CBType data, Material mat, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.UpdateData(cmd, data); cb.Set(mat, shaderId); } /// /// Update the GPU data of the constant buffer. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. public static void UpdateData(CommandBuffer cmd, in CBType data) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.UpdateData(cmd, data); } /// /// Bind the constant buffer globally. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Shader porperty id to bind the constant buffer to. public static void SetGlobal(CommandBuffer cmd, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.SetGlobal(cmd, shaderId); } /// /// Bind the constant buffer to a compute shader. /// /// The type of structure representing the constant buffer data. /// Command Buffer used to execute the graphic commands. /// Compute shader to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. public static void Set(CommandBuffer cmd, ComputeShader cs, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.Set(cmd, cs, shaderId); } /// /// Bind the constant buffer to a material. /// /// The type of structure representing the constant buffer data. /// Material to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. public static void Set(Material mat, int shaderId) where CBType : struct { var cb = ConstantBufferSingleton.instance; cb.Set(mat, shaderId); } /// /// Release all currently allocated singleton constant buffers. /// This needs to be called before shutting down the application. /// 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); } } /// /// The base class of Constant Buffer. /// public abstract class ConstantBufferBase { /// /// Release the constant buffer. /// public abstract void Release(); } /// /// An instance of a constant buffer. /// /// The type of structure representing the constant buffer data. public class ConstantBuffer : ConstantBufferBase where CBType : struct { // Used to track all global bindings used by this CB type. HashSet m_GlobalBindings = new HashSet(); // Array is required by the ComputeBuffer SetData API CBType[] m_Data = new CBType[1]; ComputeBuffer m_GPUConstantBuffer = null; /// /// Constant Buffer constructor. /// public ConstantBuffer() { m_GPUConstantBuffer = new ComputeBuffer(1, UnsafeUtility.SizeOf(), ComputeBufferType.Constant); } /// /// Update the GPU data of the constant buffer. /// /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. 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 } /// /// Bind the constant buffer globally. /// /// Command Buffer used to execute the graphic commands. /// Shader porperty id to bind the constant buffer to. public void SetGlobal(CommandBuffer cmd, int shaderId) { m_GlobalBindings.Add(shaderId); cmd.SetGlobalConstantBuffer(m_GPUConstantBuffer, shaderId, 0, m_GPUConstantBuffer.stride); } /// /// Bind the constant buffer to a compute shader. /// /// Command Buffer used to execute the graphic commands. /// Compute shader to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. public void Set(CommandBuffer cmd, ComputeShader cs, int shaderId) { cmd.SetComputeConstantBufferParam(cs, shaderId, m_GPUConstantBuffer, 0, m_GPUConstantBuffer.stride); } /// /// Bind the constant buffer to a material. /// /// Material to which the constant buffer should be bound. /// Shader porperty id to bind the constant buffer to. 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); } /// /// Update the GPU data of the constant buffer and bind it globally. /// /// Command Buffer used to execute the graphic commands. /// Input data of the constant buffer. /// Shader porperty id to bind the constant buffer to. public void PushGlobal(CommandBuffer cmd, in CBType data, int shaderId) { UpdateData(cmd, data); SetGlobal(cmd, shaderId); } /// /// Release the constant buffers. /// 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 : ConstantBuffer where CBType : struct { static ConstantBufferSingleton s_Instance = null; internal static ConstantBufferSingleton instance { get { if (s_Instance == null) { s_Instance = new ConstantBufferSingleton(); ConstantBuffer.Register(s_Instance); } return s_Instance; } set { s_Instance = value; } } public override void Release() { base.Release(); s_Instance = null; } } }