using System; using System.IO; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Rendering.UnifiedRayTracing { /// /// Specifies what backend to use when creating a . /// public enum RayTracingBackend { /// /// Requires a GPU supporting hardware accelerated ray tracing. /// Hardware = 0, /// /// Software implementation of ray tracing that requires the GPU to support compute shaders. /// Compute = 1 } /// /// Entry point for the UnifiedRayTracing API. /// /// /// It provides functionality to: /// /// load shader code (CreateRayTracingShader) /// create an acceleration structure (CreateAccelerationStructure) that represents the geometry to be ray traced against. /// /// Once these objects have been created, the shader code can be executed by calling IRayTracingShader.Dispatch /// Before calling Dispose() on a RayTracingContext, all that have been created by a RayTracingContext must be disposed as well. /// public sealed class RayTracingContext : IDisposable { /// /// Creates a RayTracingContext. /// /// The chosen backend. /// The resources (provides the various shaders the context needs to operate). /// Thrown when the supplied backend is not supported. public RayTracingContext(RayTracingBackend backend, RayTracingResources resources) { Utils.CheckArgIsNotNull(resources, nameof(resources)); if (!IsBackendSupported(backend)) throw new System.InvalidOperationException("Unsupported backend: " + backend.ToString()); BackendType = backend; if (backend == RayTracingBackend.Hardware) m_Backend = new HardwareRayTracingBackend(resources); else if (backend == RayTracingBackend.Compute) m_Backend = new ComputeRayTracingBackend(resources); Resources = resources; m_DispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); } /// /// Creates a RayTracingContext. /// /// The resources (provides the various shaders the context needs to operate). /// Thrown when no supported backend is available. public RayTracingContext(RayTracingResources resources) : this(IsBackendSupported(RayTracingBackend.Hardware) ? RayTracingBackend.Hardware : RayTracingBackend.Compute, resources) { } /// /// Disposes the RaytracingContext. /// /// /// Before calling this, all that have been created with this RayTracingContext must be disposed as well. /// public void Dispose() { if (m_AccelStructCounter.value != 0) { Debug.LogError("Memory Leak. Please call .Dispose() on all the IAccelerationStructure resources "+ "that have been created with this context before calling RayTracingContext.Dispose()"); } m_DispatchBuffer?.Release(); } /// /// object this context has been created with. /// public RayTracingResources Resources { get; private set; } /// /// Checks if the specified backend is supported on the current GPU. /// /// The backend. /// Whether the specified bakend is supported. static public bool IsBackendSupported(RayTracingBackend backend) { if (backend == RayTracingBackend.Hardware) return SystemInfo.supportsRayTracing; else if (backend == RayTracingBackend.Compute) return SystemInfo.supportsComputeShaders; return false; } /// /// Creates a IRayTracingShader. /// /// /// Depending on the chosen backend, the shader parameter /// needs to be either a ComputeShader or RayTracingShader. /// /// The ComputeShader or RayTracingShader asset. /// The unified ray tracing shader. public IRayTracingShader CreateRayTracingShader(Object shader) => m_Backend.CreateRayTracingShader(shader, "MainRayGenShader", m_DispatchBuffer); #if UNITY_EDITOR /// /// Creates a unified ray tracing shader from .urtshader asset file. /// /// /// - This API works only in the Unity Editor, not at runtime. /// - The path must be relative to the project folder, for example: "Assets/Stuff/myshader.urtshader". /// - A .urtshader asset file is imported in the Editor as 2 shaders: a ComputeShader and a RayTracingShader. LoadRayTracingShader loads the one relevant one depending on the RayTracingContext's backend. /// /// Path to the .urtshader shader asset file to load. /// The unified ray tracing shader. public IRayTracingShader LoadRayTracingShader(string fileName) { Type shaderType = BackendHelpers.GetTypeOfShader(BackendType); Object asset = AssetDatabase.LoadAssetAtPath(fileName, shaderType); return CreateRayTracingShader(asset); } #endif #if ENABLE_ASSET_BUNDLE /// /// Creates a unified ray tracing shader from an AssetBundle. /// /// The AssetBundle. /// The asset name with the .urtshader extension included. /// The unified ray tracing shader. public IRayTracingShader LoadRayTracingShaderFromAssetBundle(AssetBundle assetBundle, string name) { Utils.CheckArgIsNotNull(assetBundle, nameof(assetBundle)); Object asset = assetBundle.LoadAsset(name, BackendHelpers.GetTypeOfShader(BackendType)); return CreateRayTracingShader(asset); } #endif /// /// Creates a IRayTracingAccelStruct. /// /// Options for quality/performance trade-offs for the returned acceleration structure /// The acceleration structure. public IRayTracingAccelStruct CreateAccelerationStructure(AccelerationStructureOptions options) { Utils.CheckArgIsNotNull(options, nameof(options)); var accelStruct = m_Backend.CreateAccelerationStructure(options, m_AccelStructCounter); return accelStruct; } /// /// Returns the minimum size that is required by the scratchBuffer parameter of . /// /// Number of threads in the X dimension. /// Number of threads in the Y dimension. /// Number of threads in the Z dimension. /// The size in bytes. public ulong GetRequiredTraceScratchBufferSizeInBytes(uint width, uint height, uint depth) { return m_Backend.GetRequiredTraceScratchBufferSizeInBytes(width, height, depth); } /// /// Required stride for the creation of the scratchBuffers used by and . /// /// The required stride. public static uint GetScratchBufferStrideInBytes() => 4; /// /// The this context was created with. /// public RayTracingBackend BackendType { get; private set; } readonly IRayTracingBackend m_Backend; readonly ReferenceCounter m_AccelStructCounter = new ReferenceCounter(); readonly GraphicsBuffer m_DispatchBuffer; } /// /// Specifies how Unity builds the acceleration structure on the GPU. /// [System.Flags] public enum BuildFlags { /// /// Specify no options for the acceleration structure build. Provides a trade-off between good ray tracing performance and fast build times. /// None = 0, /// /// Build a high quality acceleration structure, increasing build time but maximizing ray tracing performance. /// PreferFastTrace = 1 << 0, /// /// Build a lower quality acceleration structure, minimizing build time but decreasing ray tracing performance. /// PreferFastBuild = 1 << 1, /// /// Minimize the amount of temporary memory Unity uses when building the acceleration structure, and minimize the size of the result. /// MinimizeMemory = 1 << 2 } /// /// Options used to configure the creation of a . /// public class AccelerationStructureOptions { /// /// Option for the quality of the built . /// public BuildFlags buildFlags = 0; #if UNITY_EDITOR /// /// Enables building the acceleration structure on the CPU instead of the GPU. /// Enabling this option combined with the use of the PreferFastBuild flag provides the best possible ray tracing performance. /// /// /// This field works only in the Unity Editor, not at runtime. /// public bool useCPUBuild = false; #endif } internal class ReferenceCounter { public ulong value = 0; public void Inc() { value++; } public void Dec() { value--; } } /// /// Helper functions that can be used to create a scratch buffer. /// /// /// A scratch buffer is a GraphicsBuffer that Unity uses during the acceleration structure build or the ray tracing dispatch to store temporary data. /// public static class RayTracingHelper { /// /// suitable for scratch buffers used in for both and . /// public const GraphicsBuffer.Target ScratchBufferTarget = GraphicsBuffer.Target.Structured; /// /// Creates an indirect args buffer suitable for . /// /// The scratch buffer. static public GraphicsBuffer CreateDispatchIndirectBuffer() { return new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, 3, sizeof(uint)); } /// /// Creates a scratch buffer suitable for both and . /// /// The acceleration structure that will be passed to . /// The shader that will be passed to . /// Number of threads in the X dimension that will be passed to . /// Number of threads in the Y dimension that will be passed to . /// Number of threads in the Z dimension that will be passed to . /// The scratch buffer. static public GraphicsBuffer CreateScratchBufferForBuildAndDispatch( IRayTracingAccelStruct accelStruct, IRayTracingShader shader, uint dispatchWidth, uint dispatchHeight, uint dispatchDepth) { Utils.CheckArgIsNotNull(accelStruct, nameof(accelStruct)); Utils.CheckArgIsNotNull(shader, nameof(shader)); var sizeInBytes = System.Math.Max(accelStruct.GetBuildScratchBufferRequiredSizeInBytes(), shader.GetTraceScratchBufferRequiredSizeInBytes(dispatchWidth, dispatchHeight, dispatchDepth)); if (sizeInBytes == 0) return null; return new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)(sizeInBytes / 4), 4); } /// /// Creates a scratch buffer suitable for . /// /// The acceleration structure that will be passed to . /// The scratch buffer. static public GraphicsBuffer CreateScratchBufferForBuild( IRayTracingAccelStruct accelStruct) { Utils.CheckArgIsNotNull(accelStruct, nameof(accelStruct)); var sizeInBytes = accelStruct.GetBuildScratchBufferRequiredSizeInBytes(); if (sizeInBytes == 0) return null; return new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)(sizeInBytes / 4), 4); } /// /// Creates a scratch buffer suitable for . /// /// The shader that will be passed to . /// Number of threads in the X dimension that will be passed to . /// Number of threads in the Y dimension that will be passed to . /// Number of threads in the Z dimension that will be passed to . /// The scratch buffer. static public GraphicsBuffer CreateScratchBufferForTrace(IRayTracingShader shader, uint dispatchWidth, uint dispatchHeight, uint dispatchDepth) { Utils.CheckArgIsNotNull(shader, nameof(shader)); var sizeInBytes = shader.GetTraceScratchBufferRequiredSizeInBytes(dispatchWidth, dispatchHeight, dispatchDepth); if (sizeInBytes == 0) return null; return new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)(sizeInBytes / 4), 4); } /// /// Resizes a scratch buffer if its size doesn't fit the requirement of . /// /// /// The resize is accomplished by disposing of the GraphicsBuffer and instanciating a new one at the proper size. /// /// The shader that will be passed to . /// Number of threads in the X dimension that will be passed to . /// Number of threads in the Y dimension that will be passed to . /// Number of threads in the Z dimension that will be passed to . /// The scratch buffer. static public void ResizeScratchBufferForTrace( IRayTracingShader shader, uint dispatchWidth, uint dispatchHeight, uint dispatchDepth, ref GraphicsBuffer scratchBuffer) { Utils.CheckArgIsNotNull(shader, nameof(shader)); var sizeInBytes = shader.GetTraceScratchBufferRequiredSizeInBytes(dispatchWidth, dispatchHeight, dispatchDepth); if (sizeInBytes == 0) return; if (scratchBuffer != null) Utils.CheckArg(scratchBuffer.target == ScratchBufferTarget, "scratchBuffer.target must have Target.Structured set"); if (scratchBuffer == null || (ulong)(scratchBuffer.count*scratchBuffer.stride) < sizeInBytes) { scratchBuffer?.Dispose(); scratchBuffer = new GraphicsBuffer(ScratchBufferTarget, (int)(sizeInBytes / 4), 4); } } /// /// Resizes a scratch buffer if its size doesn't fit the requirement of . /// /// /// The resize is accomplished by disposing of the GraphicsBuffer and instanciating a new one at the proper size. /// /// The acceleration structure that will be passed to . /// The scratch buffer. static public void ResizeScratchBufferForBuild( IRayTracingAccelStruct accelStruct, ref GraphicsBuffer scratchBuffer) { Utils.CheckArgIsNotNull(accelStruct, nameof(accelStruct)); var sizeInBytes = accelStruct.GetBuildScratchBufferRequiredSizeInBytes(); if (sizeInBytes == 0) return; if (scratchBuffer != null) Utils.CheckArg(scratchBuffer.target == ScratchBufferTarget, "scratchBuffer.target must have Target.Structured set"); if (scratchBuffer == null || (ulong)(scratchBuffer.count * scratchBuffer.stride) < sizeInBytes) { scratchBuffer?.Dispose(); scratchBuffer = new GraphicsBuffer(ScratchBufferTarget, (int)(sizeInBytes / 4), 4); } } } }