You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

389 lines
19 KiB

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