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