diff --git a/Packages/com.unity.render-pipelines.core/CHANGELOG.md b/Packages/com.unity.render-pipelines.core/CHANGELOG.md index b7f3ba2481b..d8f904c7a13 100644 --- a/Packages/com.unity.render-pipelines.core/CHANGELOG.md +++ b/Packages/com.unity.render-pipelines.core/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Version Updated The version number for this package has increased due to a version update of a related graphics package. +## [17.3.0] - 2025-08-27 + +This version is compatible with Unity 6000.3.0b1. +For the release notes, refer to the [Unity download archive](https://unity.com/releases/editor/archive). + ## [17.2.0] - 2025-05-14 This version is compatible with Unity 6000.2.0b2. diff --git a/Packages/com.unity.visualeffectgraph/Samples~/VFXLearningTemplates/Scenes/HDRP_VFX_Learning_Templates.meta b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools.meta similarity index 77% rename from Packages/com.unity.visualeffectgraph/Samples~/VFXLearningTemplates/Scenes/HDRP_VFX_Learning_Templates.meta rename to Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools.meta index 6427ba2f3a0..8650ffa80ed 100644 --- a/Packages/com.unity.visualeffectgraph/Samples~/VFXLearningTemplates/Scenes/HDRP_VFX_Learning_Templates.meta +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 95407ce4bfe833441adfd54b165e8802 +guid: ed5902d52dff71d41be76701cb53d637 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/.buginfo b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/.buginfo new file mode 100644 index 00000000000..a8e3ae67ae4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/.buginfo @@ -0,0 +1 @@ +area: Graphics Tools \ No newline at end of file diff --git a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters.meta b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter.meta similarity index 77% rename from Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters.meta rename to Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter.meta index 45be711f25b..4fcb04ba5ce 100644 --- a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters.meta +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6283a6a340b064386bb4795b72d73960 +guid: c26ebf53f67367948b7333fa701e7ce7 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs new file mode 100644 index 00000000000..0fea81b9800 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System; + +namespace UnityEditor.Rendering.Converter +{ + /// + /// Represents a converter that processes render pipeline conversion items. + /// + interface IRenderPipelineConverter + { + /// + /// Gets a value indicating whether the converter is enabled and can be used. + /// + bool isEnabled { get; set; } + + /// + /// Gets or sets the reason message shown when the converter item is disabled. + /// + string isDisabledMessage { get; set; } + + /// + /// Scans for available render pipeline converter items. + /// + /// + /// A callback invoked when the scan is complete, providing the list of converter items. + /// + void Scan(Action> onScanFinish); + + /// + /// Called before the conversion process begins. + /// + void BeforeConvert() { } + + /// + /// Performs the conversion on a given converter item. + /// + /// The converter item to be processed. + /// + /// An output message providing additional details about the conversion result. + /// + /// + /// A value representing the outcome of the conversion. + /// + Status Convert(IRenderPipelineConverterItem item, out string message); + + /// + /// Called after the conversion process is completed. + /// + void AfterConvert() { } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs.meta b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs.meta new file mode 100644 index 00000000000..fed0a13c589 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 48c6da6bbe0066b4892707208d54a626 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs new file mode 100644 index 00000000000..a38252ec220 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs @@ -0,0 +1,34 @@ +namespace UnityEditor.Rendering.Converter +{ + /// + /// Represents a converter item used within a render pipeline conversion process. + /// + interface IRenderPipelineConverterItem + { + /// + /// Gets the display name of the converter item. + /// + string name { get; } + + /// + /// Gets a description or additional information about the converter item. + /// + string info { get; } + + /// + /// Gets or sets a value indicating whether the converter item is enabled. + /// + bool isEnabled { get; set; } + + /// + /// Gets or sets the reason message shown when the converter item is disabled. + /// + string isDisabledMessage { get; set; } + + /// + /// Invoked when the converter item is clicked or activated. + /// + void OnClicked(); + } + +} diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs.meta b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs.meta new file mode 100644 index 00000000000..98980d56a9a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/IRenderPipelineConverterItem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 10a4df7eb002da04c922af9ac6bbbda4 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs new file mode 100644 index 00000000000..eca30445ad3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs @@ -0,0 +1,31 @@ +using System; + +namespace UnityEditor.Rendering.Converter +{ + /// + /// Represents the conversion state of a render pipeline converter item. + /// + [Serializable] + enum Status + { + /// + /// The item is waiting to be processed. + /// + Pending, + + /// + /// The item has a potential issue that may require attention. + /// + Warning, + + /// + /// The item encountered an error during processing. + /// + Error, + + /// + /// The item was successfully processed without issues. + /// + Success, + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs.meta b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs.meta new file mode 100644 index 00000000000..88cf9dd3fb0 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor-PrivateShared/Tools/Converter/Status.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b1bc4c43bfb61048817bb1757f4ac02 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/Deprecated.cs b/Packages/com.unity.render-pipelines.core/Editor/Deprecated.cs index 9082947c7cb..78dcfb6c0c0 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Deprecated.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Deprecated.cs @@ -233,4 +233,37 @@ protected override uint DoGUI(Rect rect, GUIContent label, DebugUI.MaskField fie return (uint)mask; } } + + /// + /// Interface to add additional gizmo renders for a + /// + [Obsolete("IVolumeAdditionalGizmo is no longer used. #from(6000.4)", false)] + public interface IVolumeAdditionalGizmo + { + /// + /// The type that overrides this additional gizmo + /// + Type type { get; } + + /// + /// Additional gizmo draw for + /// + /// The + /// The + void OnBoxColliderDraw(IVolume scr, BoxCollider c); + + /// + /// Additional gizmo draw for + /// + /// The + /// The + void OnSphereColliderDraw(IVolume scr, SphereCollider c); + + /// + /// Additional gizmo draw for + /// + /// The + /// The + void OnMeshColliderDraw(IVolume scr, MeshCollider c); + } } diff --git a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing.meta similarity index 77% rename from Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters.meta rename to Packages/com.unity.render-pipelines.core/Editor/PathTracing.meta index 914bdd05834..8d471886e93 100644 --- a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters.meta +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a9c3ccae4deba4e30a70dd934f32c194 +guid: 04341c513452b9f47a954b5dd19e5498 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/.buginfo b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/.buginfo new file mode 100644 index 00000000000..484b54b848d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/.buginfo @@ -0,0 +1 @@ +area: Lighting diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs new file mode 100644 index 00000000000..e5162a07605 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs.meta new file mode 100644 index 00000000000..ed8e9dd1ec8 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/AssemblyInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2077bbdc519e4f4797674f4578bf3618 +timeCreated: 1729681965 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs new file mode 100644 index 00000000000..216adae090d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +namespace UnityEditor.PathTracing.LightBakerBridge +{ + // This must be in its own file, otherwise the associated ScriptedImporter will malfunction. + internal class BakeImport : ScriptableObject + { + public string BakeInputPath; + public string LightmapRequestsPath; + public string LightProbeRequestsPath; + public string BakeOutputFolderPath; + public int ProgressPort; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs.meta new file mode 100644 index 00000000000..805571c19b7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeImport.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 01a25e37e39f89141a341cacf285acc4 diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs new file mode 100644 index 00000000000..48e6a00c310 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs @@ -0,0 +1,1049 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using Unity.Mathematics; +using UnityEngine; +using Unity.Collections.LowLevel.Unsafe; + +// The types defined in this file should match the types defined in BakeInput.h. +namespace UnityEditor.PathTracing.LightBakerBridge +{ + internal interface IBakeInputVisitable + { + void Transfer(IBakeInputVisitor visitor); + } + + internal delegate void TransferFunction(IBakeInputVisitor visitor, ref T result); + + internal interface IBakeInputVisitor + { + // Booleans and strings need special handling as they are not blittable types. + public void TransferBoolean(ref bool result); + + public void TransferString(ref string result); + + public void TransferArray(ref T[] array, TransferFunction transfer); + + public void TransferBlittable(ref T result) + where T : unmanaged; + + public void TransferBlittableArray(ref T[] array) + where T : unmanaged; + + public void TransferDictionary(ref Dictionary dict, TransferFunction valueTransfer) + where TKey : unmanaged; + } + + internal static class BakeInputVisitorExtensions + { + public static void Transfer(this IBakeInputVisitor self, ref T result) + where T : IBakeInputVisitable => result.Transfer(self); + + public static void TransferArray(this IBakeInputVisitor self, ref T[] array) where T : IBakeInputVisitable + => self.TransferArray(ref array, (IBakeInputVisitor visitor, ref T result) => visitor.Transfer(ref result)); + + public static void TransferBlittableDictionary(this IBakeInputVisitor self, ref Dictionary dict) + where TKey : unmanaged + where TValue : unmanaged + { + self.TransferDictionary(ref dict, (IBakeInputVisitor visitor, ref TValue result) => visitor.TransferBlittable(ref result)); + } + } + + internal class BakeInputReader : IBakeInputVisitor + { + private int _position; + private byte[] _bytes; + + public BakeInputReader(byte[] bytes) + { + _position = 0; + _bytes = bytes; + } + + public void TransferBoolean(ref bool result) + { + result = _bytes[_position] != 0; + _position += sizeof(byte); + } + + public void TransferString(ref string result) + { + byte[] raw = null; + this.TransferBlittableArray(ref raw); + result = System.Text.Encoding.ASCII.GetString(raw); + } + + public void TransferArray(ref T[] array, TransferFunction transfer) + { + UInt64 length = 0; + TransferBlittable(ref length); + array = new T[length]; + for (int i = 0; i < (int)length; i++) + { + transfer(this, ref array[i]); + } + } + + public unsafe void TransferBlittable(ref T result) + where T : unmanaged + { + var size = sizeof(T); + fixed (byte* ptr = &_bytes[_position]) + { + UnsafeUtility.CopyPtrToStructure(ptr, out result); + _position += size; + } + } + + public unsafe void TransferBlittableArray(ref T[] array) + where T : unmanaged + { + UInt64 length = 0; + TransferBlittable(ref length); + + array = new T[length]; + + if (0 == length) // This avoids going out-of-bounds below when we are at the end of data. + return; + + // Pin the managed arrays while we copy data over + int byteLength = (int)length * sizeof(T); + fixed (byte* ptr = &_bytes[_position]) + { + fixed (T* arrayPtr = array) + { + UnsafeUtility.MemCpy(arrayPtr, ptr, byteLength); + _position += byteLength; + } + } + } + + public void TransferDictionary(ref Dictionary dict, TransferFunction valueTransfer) + where TKey : unmanaged + { + UInt64 length = 0; + TransferBlittable(ref length); + dict = new Dictionary((int)length); + for (int i = 0; i < (int)length; i++) + { + TKey key = default; + TransferBlittable(ref key); + TValue value = default; + valueTransfer(this, ref value); + dict.Add(key, value); + } + } + } + + internal class BakeInputWriter : IBakeInputVisitor + { + private List _outBytes; + + public BakeInputWriter(List outBytes) + { + _outBytes = outBytes; + } + + public void TransferBoolean(ref bool result) => _outBytes.Add(result ? (byte)1 : (byte)0); + + public void TransferString(ref string result) + { + byte[] raw = System.Text.Encoding.ASCII.GetBytes(result); + this.TransferBlittableArray(ref raw); + } + + public void TransferArray(ref T[] array, TransferFunction transfer) + { + UInt64 length = (UInt64)array.Length; + TransferBlittable(ref length); + for (int i = 0; i < (int)length; i++) + { + transfer(this, ref array[i]); + } + } + + public unsafe void TransferBlittable(ref T result) + where T : unmanaged + { + var size = sizeof(T); + byte[] bytes = new byte[size]; + fixed (byte* ptr = &bytes[0]) + { + UnsafeUtility.CopyStructureToPtr(ref result, ptr); + } + _outBytes.AddRange(bytes); + } + + public void TransferBlittableArray(ref T[] array) + where T : unmanaged => TransferArray(ref array, (IBakeInputVisitor visitor, ref T result) => visitor.TransferBlittable(ref result)); + + public void TransferDictionary(ref Dictionary dict, TransferFunction valueTransfer) + where TKey : unmanaged + { + UInt64 length = (UInt64)dict.Count; + TransferBlittable(ref length); + + foreach (var (key, value) in dict) + { + TKey keyCopy = key; + TransferBlittable(ref keyCopy); + TValue valueCopy = value; + valueTransfer(this, ref valueCopy); + } + } + } + + internal enum Backend + { + CPU = 0, + GPU, + UnityComputeGPU, + } + + internal enum TransmissionType + { + Opacity = 0, + Transparency, + None + } + + internal enum TransmissionChannels + { + Red = 0, + Alpha, + AlphaCutout, + RGB, + None + } + + internal enum LightmapBakeMode + { + NonDirectional = 0, + CombinedDirectional + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SampleCount : IBakeInputVisitable + { + public UInt32 directSampleCount; + public UInt32 indirectSampleCount; + public UInt32 environmentSampleCount; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref directSampleCount); + visitor.TransferBlittable(ref indirectSampleCount); + visitor.TransferBlittable(ref environmentSampleCount); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LightingSettings : IBakeInputVisitable + { + public SampleCount lightmapSampleCounts; + public SampleCount probeSampleCounts; + public UInt32 minBounces; + public UInt32 maxBounces; + public LightmapBakeMode lightmapBakeMode; + public MixedLightingMode mixedLightingMode; + public bool aoEnabled; + public float aoDistance; + public bool useHardwareRayTracing; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.Transfer(ref lightmapSampleCounts); + visitor.Transfer(ref probeSampleCounts); + visitor.TransferBlittable(ref minBounces); + visitor.TransferBlittable(ref maxBounces); + visitor.TransferBlittable(ref lightmapBakeMode); + visitor.TransferBlittable(ref mixedLightingMode); + visitor.TransferBoolean(ref aoEnabled); + visitor.TransferBlittable(ref aoDistance); + visitor.TransferBlittable(ref useHardwareRayTracing); + } + } + + internal enum MeshShaderChannel + { + None = -1, + Vertex = 0, + Normal = 1, + TexCoord0 = 2, + TexCoord1 = 3, + Count = 4 + } + + [Flags] + internal enum MeshShaderChannelMask + { + Invalid = -1, + Empty = 0, + Vertex = 1 << MeshShaderChannel.Vertex, + Normal = 1 << MeshShaderChannel.Normal, + TexCoord0 = 1 << MeshShaderChannel.TexCoord0, + TexCoord1 = 1 << MeshShaderChannel.TexCoord1, + MaskAll = (1 << MeshShaderChannel.Count) - 1 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct VertexData : IBakeInputVisitable + { + public UInt32 vertexCount; + public MeshShaderChannelMask meshShaderChannelMask; + public UInt32[] dimensions; // number of float comprising the channel item + public UInt32[] offsets; // offset to channel item in bytes + public UInt32[] stride; // stride between channel items in bytes + public byte[] data; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref vertexCount); + visitor.TransferBlittable(ref meshShaderChannelMask); + visitor.TransferBlittableArray(ref dimensions); + visitor.TransferBlittableArray(ref offsets); + visitor.TransferBlittableArray(ref stride); + visitor.TransferBlittableArray(ref data); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MeshData : IBakeInputVisitable + { + public VertexData vertexData; + public UInt32[] indexBuffer; + public UInt32[] subMeshIndexOffset; + public UInt32[] subMeshIndexCount; + public Bounds[] subMeshAABB; + public float4 uvScaleOffset; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.Transfer(ref vertexData); + visitor.TransferBlittableArray(ref indexBuffer); + visitor.TransferBlittableArray(ref subMeshIndexOffset); + visitor.TransferBlittableArray(ref subMeshIndexCount); + visitor.TransferBlittableArray(ref subMeshAABB); + visitor.TransferBlittable(ref uvScaleOffset); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MaterialData : IBakeInputVisitable + { + public bool doubleSidedGI; + public TransmissionType transmissionType; + public TransmissionChannels transmissionChannels; + public float alphaCutoff; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBoolean(ref doubleSidedGI); + visitor.TransferBlittable(ref transmissionType); + visitor.TransferBlittable(ref transmissionChannels); + visitor.TransferBlittable(ref alphaCutoff); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct HeightmapData : IBakeInputVisitable + { + public Int16[] data; + public UInt16 resolution; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittableArray(ref data); + visitor.TransferBlittable(ref resolution); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TerrainHoleData : IBakeInputVisitable + { + public byte[] data; + public UInt16 resolution; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittableArray(ref data); + visitor.TransferBlittable(ref resolution); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TerrainData : IBakeInputVisitable + { + public UInt32 heightMapIndex; // index into BakeInput::m_HeightmapData + public Int32 terrainHoleIndex; // index into BakeInput::m_TerrainHoleData -1 means no hole data + public float outputResolution; + public float3 heightmapScale; + public float4 uvBounds; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref heightMapIndex); + visitor.TransferBlittable(ref terrainHoleIndex); + visitor.TransferBlittable(ref outputResolution); + visitor.TransferBlittable(ref heightmapScale); + visitor.TransferBlittable(ref uvBounds); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct InstanceData : IBakeInputVisitable + { + public Int32 meshIndex; // index into BakeInput::m_MeshData, -1 for Terrain + public Int32 terrainIndex; // index into BakeInput::m_TerrainData, -1 for MeshRenderer + public float4x4 transform; + public bool oddNegativeScale; + public bool castShadows; + public bool receiveShadows; + public Int32 lodGroup; + public byte lodMask; + public Int32[] subMeshMaterialIndices; // -1 is no material for a given subMesh entry + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref meshIndex); + visitor.TransferBlittable(ref terrainIndex); + visitor.TransferBlittable(ref transform); + visitor.TransferBoolean(ref oddNegativeScale); + visitor.TransferBoolean(ref castShadows); + visitor.TransferBoolean(ref receiveShadows); + visitor.TransferBlittable(ref lodGroup); + visitor.TransferBlittable(ref lodMask); + visitor.TransferBlittableArray(ref subMeshMaterialIndices); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TextureData : IBakeInputVisitable + { + public UInt32 width; + public UInt32 height; + public float4[] data; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref width); + visitor.TransferBlittable(ref height); + visitor.TransferBlittableArray(ref data); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TextureTransformData : IBakeInputVisitable + { + public float2 scale; + public float2 offset; + + public void Transfer(IBakeInputVisitor visitor) + { + float4 data = default; + visitor.TransferBlittable(ref data); + scale = data.xy; + offset = data.zw; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TextureProperties : IBakeInputVisitable + { + public TextureWrapMode wrapModeU; + public TextureWrapMode wrapModeV; + public FilterMode filterMode; + public TextureTransformData transmissionTextureST; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref wrapModeU); + visitor.TransferBlittable(ref wrapModeV); + visitor.TransferBlittable(ref filterMode); + visitor.Transfer(ref transmissionTextureST); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct EnvironmentData : IBakeInputVisitable + { + public UInt32 cubeResolution; + public float4[] cubeData; + public UInt32 importanceSampleCount; + public float importanceIntegratedMetric; + public float4[] importanceDirections; + public float4[] importanceWeightedIntensities; + public float4[] importanceIntensities; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref cubeResolution); + visitor.TransferBlittableArray(ref cubeData); + visitor.TransferBlittable(ref importanceSampleCount); + visitor.TransferBlittable(ref importanceIntegratedMetric); + visitor.TransferBlittableArray(ref importanceDirections); + visitor.TransferBlittableArray(ref importanceWeightedIntensities); + visitor.TransferBlittableArray(ref importanceIntensities); + } + } + + internal enum LightType : byte + { + Directional = 0, + Point, + Spot, + Rectangle, + Disc, + SpotPyramidShape, + SpotBoxShape + } + + internal enum FalloffType : byte + { + InverseSquared = 0, + InverseSquaredNoRangeAttenuation, + Linear, + Legacy, + None + } + + internal enum AngularFalloffType : byte + { + LUT = 0, + AnalyticAndInnerAngle + } + + internal enum LightMode : byte + { + Realtime = 0, + Mixed, + Baked + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LightData : IBakeInputVisitable + { + // shared + public float3 color; + public float3 indirectColor; + public Quaternion orientation; + public float3 position; + public float range; + + // cookie + public UInt32 cookieTextureIndex; + public float cookieScale; + + // spot light only or cookieSize for directional lights + public float coneAngle; + public float innerConeAngle; + + // area light parameters (interpretation depends on the type) + public float shape0; + public float shape1; + + public LightType type; + public LightMode mode; + public FalloffType falloff; + public AngularFalloffType angularFalloff; + public bool castsShadows; + public UInt32 shadowMaskChannel; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref color); + visitor.TransferBlittable(ref indirectColor); + visitor.TransferBlittable(ref orientation); + visitor.TransferBlittable(ref position); + visitor.TransferBlittable(ref range); + visitor.TransferBlittable(ref cookieTextureIndex); + visitor.TransferBlittable(ref cookieScale); + visitor.TransferBlittable(ref coneAngle); + visitor.TransferBlittable(ref innerConeAngle); + visitor.TransferBlittable(ref shape0); + visitor.TransferBlittable(ref shape1); + visitor.TransferBlittable(ref type); + visitor.TransferBlittable(ref mode); + visitor.TransferBlittable(ref falloff); + visitor.TransferBlittable(ref angularFalloff); + visitor.TransferBoolean(ref castsShadows); + visitor.TransferBlittable(ref shadowMaskChannel); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CookieData : IBakeInputVisitable + { + public UInt32 width; + public UInt32 height; + public UInt32 pixelStride; + public UInt32 slices; // 1 for single, 6 for cubes + public bool repeat; // texture addressing mode + public byte[] textureData; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref width); + visitor.TransferBlittable(ref height); + visitor.TransferBlittable(ref pixelStride); + visitor.TransferBlittable(ref slices); + visitor.TransferBoolean(ref repeat); + visitor.TransferBlittableArray(ref textureData); + } + } + + [Flags] + internal enum ProbeRequestOutputType : uint + { + RadianceDirect = 1 << 0, + RadianceIndirect = 1 << 1, + Validity = 1 << 2, + MixedLightOcclusion = 1 << 3, + LightProbeOcclusion = 1 << 4, + EnvironmentOcclusion = 1 << 5, + Depth = 1 << 6, + All = 0xFFFFFFFF + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ProbeRequest : IBakeInputVisitable + { + public ProbeRequestOutputType outputTypeMask; + public UInt64 positionOffset; + public UInt64 count; + public float pushoff; + public string outputFolderPath; + + public UInt64 integrationRadiusOffset; + public UInt32 environmentOcclusionSampleCount; + public bool ignoreDirectEnvironment; + public bool ignoreIndirectEnvironment; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref outputTypeMask); + visitor.TransferBlittable(ref positionOffset); + visitor.TransferBlittable(ref count); + visitor.TransferBlittable(ref pushoff); + visitor.TransferString(ref outputFolderPath); + visitor.TransferBlittable(ref integrationRadiusOffset); + visitor.TransferBlittable(ref environmentOcclusionSampleCount); + visitor.TransferBoolean(ref ignoreDirectEnvironment); + visitor.TransferBoolean(ref ignoreIndirectEnvironment); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ProbeRequestData : IBakeInputVisitable + { + public float3[] positions; + public int[] occlusionLightIndices; // 4 entries per probe, index into BakeInput.lightData + public float[] integrationRadii; + public ProbeRequest[] requests; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittableArray(ref positions); + visitor.TransferBlittableArray(ref occlusionLightIndices); + visitor.TransferBlittableArray(ref integrationRadii); + visitor.TransferArray(ref requests); + } + } + + [Flags] + internal enum LightmapRequestOutputType : uint + { + IrradianceIndirect = 1 << 0, + IrradianceDirect = 1 << 1, + IrradianceEnvironment = 1 << 2, + Occupancy = 1 << 3, + Validity = 1 << 4, + DirectionalityIndirect = 1 << 5, + DirectionalityDirect = 1 << 6, + AmbientOcclusion = 1 << 7, + Shadowmask = 1 << 8, + Normal = 1 << 9, + ChartIndex = 1 << 10, + OverlapPixelIndex = 1 << 11, + All = 0xFFFFFFFF + } + + internal enum TilingMode : byte + { // Assuming a 4k lightmap (16M texels), the tiling will yield the following chunk sizes: + None = 0, // 4k * 4k = 16M texels + Quarter = 1, // 2k * 2k = 4M texels + Sixteenth = 2, // 1k * 1k = 1M texels + Sixtyfourth = 3, // 512 * 512 = 262k texels + TwoHundredFiftySixth = 4, // 256 * 256 = 65k texels + Max = TwoHundredFiftySixth, + Error = 5 // Error. We don't want to go lower (GPU occupancy will start to be a problem for smaller atlas sizes). + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LightmapRequest : IBakeInputVisitable + { + public LightmapRequestOutputType outputTypeMask; + public UInt32 lightmapOffset; + public UInt32 lightmapCount; + public TilingMode tilingMode; + public string outputFolderPath; + public float pushoff; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref outputTypeMask); + visitor.TransferBlittable(ref lightmapOffset); + visitor.TransferBlittable(ref lightmapCount); + visitor.TransferBlittable(ref tilingMode); + visitor.TransferString(ref outputFolderPath); + visitor.TransferBlittable(ref pushoff); + } + + public static UInt64 TilingModeToLightmapExpandedBufferSize(TilingMode tilingMode) + { + UInt64 kMinBufferSize = 64; + UInt64 bufferSize = 0; + // TODO: We need to change the naming of the entries in the enum see: https://jira.unity3d.com/browse/GFXFEAT-728 + switch (tilingMode) + { + case TilingMode.None: bufferSize = 1048576; break; // UI: Highest Performance + case TilingMode.Quarter: bufferSize = 524288; break; // UI: High Performance + case TilingMode.Sixteenth: bufferSize = 262144; break; // UI: Automatic (but it is not automatic) + case TilingMode.Sixtyfourth: bufferSize = 131072; break; // UI: Low Memory Usage + case TilingMode.TwoHundredFiftySixth: bufferSize = 65536; break; // UI: Lowest Memory Usage + default: Debug.Assert(false, "Unknown tiling mode."); break; + } + return math.max(bufferSize, kMinBufferSize); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ProgressiveBakeParametersStruct + { + public enum SupersamplingMultiplier + { + kSupersamplingDisabled = 1, + kSupersamplingx2 = 2, + kSupersamplingx4 = 4 + } + + public float backfaceTolerance; + public SupersamplingMultiplier supersamplingMultiplier; + public float pushOff; + public int bakedLightmapTag; + public int maxLightmapCount; + } + + // This struct has the same binary layout as Hash128, but represents how we use 'fake' hashes + // to store indices in LightBaker. + [StructLayout(LayoutKind.Sequential)] + internal struct IndexHash128 : IEquatable + { + internal ulong _u64First; + internal ulong _u64Second; + + public ulong Index => _u64First; + + public override int GetHashCode() => HashCode.Combine(_u64First, _u64Second); + public bool Equals(IndexHash128 other) => _u64First == other._u64First && _u64Second == other._u64Second; + public override bool Equals(object obj) => obj is IndexHash128 other && Equals(other); + public static bool operator ==(IndexHash128 a, IndexHash128 b) => a.Equals(b); + public static bool operator !=(IndexHash128 a, IndexHash128 b) => !a.Equals(b); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PVRAtlasData : IBakeInputVisitable + { + public IndexHash128 m_AtlasHash; // The hash of this atlas. + public IndexHash128 m_SceneGUID; // The scene identifier, used for multi-scene bakes. + public Int32 m_AtlasId; // The atlasId. + public ProgressiveBakeParametersStruct m_BakeParameters; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref m_AtlasHash); + visitor.TransferBlittable(ref m_SceneGUID); + visitor.TransferBlittable(ref m_AtlasId); + visitor.TransferBlittable(ref m_BakeParameters); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GBufferInstanceData : IBakeInputVisitable + { + public IndexHash128 objectIDHash; + public IndexHash128 geometryHashPVR; + public float4 st; + public int transformIndex; + public Rect atlasViewport; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref objectIDHash); + visitor.TransferBlittable(ref geometryHashPVR); + visitor.TransferBlittable(ref st); + visitor.TransferBlittable(ref transformIndex); + visitor.TransferBlittable(ref atlasViewport); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GBufferInstances : IBakeInputVisitable + { + public GBufferInstanceData[] gbufferInstanceDataArray; + public int atlasId; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferArray(ref gbufferInstanceDataArray); + visitor.TransferBlittable(ref atlasId); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct AtlassedInstanceData : IBakeInputVisitable + { + public int m_AtlasId; // The 0-based atlas index of the atlas (used by the renderers to get the lightmap). + public int m_InstanceIndex; // Instance index in atlas. + public float4 m_LightmapST; // The atlas UV scale and translate. + public Rect m_Viewport; + public float m_Width; + public float m_Height; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferBlittable(ref m_AtlasId); + visitor.TransferBlittable(ref m_InstanceIndex); + visitor.TransferBlittable(ref m_LightmapST); + visitor.TransferBlittable(ref m_Viewport); + visitor.TransferBlittable(ref m_Width); + visitor.TransferBlittable(ref m_Height); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PVRAtlassingData : IBakeInputVisitable + { + public PVRAtlasData[] m_AtlasIdToAtlasHash; + public PVRAtlasData[] m_AtlasIdToAtlasHashLightmapped; + public (int width, int height)[] m_AtlasSizes; + public Dictionary m_AtlasHashToAtlasId; + public Dictionary m_AtlasHashToGBufferHash; + public IndexHash128[] m_GBufferHashes; + public Dictionary m_AtlasHashToObjectIDHashes; + public Dictionary m_AtlasHashToAtlasWeight; + public Dictionary m_GBufferHashToGBufferInstances; + public Dictionary m_InstanceAtlassingData; + public int[] m_AtlasOffsets; + public double m_EstimatedTexelCount; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.TransferArray(ref m_AtlasIdToAtlasHash); + visitor.TransferArray(ref m_AtlasIdToAtlasHashLightmapped); + visitor.TransferBlittableArray(ref m_AtlasSizes); + visitor.TransferBlittableDictionary(ref m_AtlasHashToAtlasId); + visitor.TransferBlittableDictionary(ref m_AtlasHashToGBufferHash); + visitor.TransferBlittableArray(ref m_GBufferHashes); + visitor.TransferDictionary(ref m_AtlasHashToObjectIDHashes, (IBakeInputVisitor dictionaryVisitor, ref IndexHash128[] result) => dictionaryVisitor.TransferBlittableArray(ref result)); + visitor.TransferBlittableDictionary(ref m_AtlasHashToAtlasWeight); + visitor.TransferDictionary(ref m_GBufferHashToGBufferInstances, (IBakeInputVisitor dictionaryVisitor, ref GBufferInstances result) => dictionaryVisitor.Transfer(ref result)); + visitor.TransferBlittableDictionary(ref m_InstanceAtlassingData); + visitor.TransferBlittableArray(ref m_AtlasOffsets); + visitor.TransferBlittable(ref m_EstimatedTexelCount); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LightmapRequestData : IBakeInputVisitable + { + public PVRAtlassingData atlassing; + public LightmapRequest[] requests; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.Transfer(ref atlassing); + visitor.TransferArray(ref requests); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BakeInput : IBakeInputVisitable + { + // Global settings + public LightingSettings lightingSettings; + // Mesh data + public MeshData[] meshData; + public TerrainData[] terrainData; + public TerrainHoleData[] terrainHoleData; + public HeightmapData[] heightMapData; + // Material data + public MaterialData[] materialData; + // Instance data + public InstanceData[] instanceData; + // Texture data + public UInt32[] instanceToTextureDataIndex; // Index into albedoData and emissiveData for each instance + public Int32[] materialToTransmissionDataIndex; // Index into transmissionData and transmissionDataProperties for each material + public TextureData[] albedoData; + public TextureData[] emissiveData; + public TextureData[] transmissionData; // Same size as transmissionDataProperties + public TextureProperties[] transmissionDataProperties; // Same size as transmissionData + // Cookie data + public CookieData[] cookieData; + public LightData[] lightData; + // Environment data + public EnvironmentData environmentData; + + public void Transfer(IBakeInputVisitor visitor) + { + visitor.Transfer(ref lightingSettings); + visitor.TransferArray(ref meshData); + visitor.TransferArray(ref terrainData); + visitor.TransferArray(ref terrainHoleData); + visitor.TransferArray(ref heightMapData); + visitor.TransferArray(ref materialData); + visitor.TransferArray(ref instanceData); + visitor.TransferBlittableArray(ref instanceToTextureDataIndex); + visitor.TransferBlittableArray(ref materialToTransmissionDataIndex); + visitor.TransferArray(ref albedoData); + visitor.TransferArray(ref emissiveData); + visitor.TransferArray(ref transmissionData); + visitor.TransferArray(ref transmissionDataProperties); + visitor.TransferArray(ref cookieData); + visitor.TransferArray(ref lightData); + visitor.Transfer(ref environmentData); + } + } + + internal static class BakeInputSerialization + { + // Should match BakeInputSerialization::kCurrentFileVersion in BakeInputSerialization.h. + // If these are out of sync, the implementation in this file probably needs to be updated. + const UInt64 CurrentFileVersion = 202405161; + + public static bool Deserialize(string path, out BakeInput bakeInput) + { + BakeInputReader reader = new(File.ReadAllBytes(path)); + return Deserialize(reader, out bakeInput); + } + + public static bool Deserialize(string path, out LightmapRequestData lightmapRequestData) + { + BakeInputReader reader = new(File.ReadAllBytes(path)); + return Deserialize(reader, out lightmapRequestData); + } + + public static bool Deserialize(string path, out ProbeRequestData probeRequestData) + { + BakeInputReader reader = new(File.ReadAllBytes(path)); + return Deserialize(reader, out probeRequestData); + } + + public static bool Deserialize(byte[] memory, out BakeInput bakeInput) + { + BakeInputReader reader = new(memory); + return Deserialize(reader, out bakeInput); + } + + private static bool Deserialize(BakeInputReader visitor, out BakeInput bakeInput) + { + bakeInput = default; + + UInt64 fileVersion = 0; + visitor.TransferBlittable(ref fileVersion); + Debug.Assert(fileVersion == CurrentFileVersion, "Version number did not match the current implementation of BakeInput deserialization."); + if (fileVersion != CurrentFileVersion) + return false; + + visitor.Transfer(ref bakeInput); + + return true; + } + + private static bool Deserialize(BakeInputReader visitor, out LightmapRequestData lightmapRequestData) + { + lightmapRequestData = default; + + UInt64 fileVersion = 0; + visitor.TransferBlittable(ref fileVersion); + Debug.Assert(fileVersion == CurrentFileVersion, "Version number did not match the current implementation of LightmapRequestData deserialization."); + if (fileVersion != CurrentFileVersion) + return false; + + visitor.Transfer(ref lightmapRequestData); + + return true; + } + + private static bool Deserialize(BakeInputReader visitor, out ProbeRequestData lightProbeRequestData) + { + lightProbeRequestData = default; + + UInt64 fileVersion = 0; + visitor.TransferBlittable(ref fileVersion); + Debug.Assert(fileVersion == CurrentFileVersion, "Version number did not match the current implementation of LightProbeRequestData deserialization."); + if (fileVersion != CurrentFileVersion) + return false; + + visitor.Transfer(ref lightProbeRequestData); + + return true; + } + + public static byte[] Serialize(ref BakeInput bakeInput) + { + var bytes = new List(); + BakeInputWriter writer = new(bytes); + + UInt64 fileVersion = CurrentFileVersion; + writer.TransferBlittable(ref fileVersion); + + writer.Transfer(ref bakeInput); + + return bytes.ToArray(); + } + + public static byte[] Serialize(ref LightmapRequestData lightmapRequestData) + { + var bytes = new List(); + BakeInputWriter writer = new(bytes); + + UInt64 fileVersion = CurrentFileVersion; + writer.TransferBlittable(ref fileVersion); + + writer.Transfer(ref lightmapRequestData); + + return bytes.ToArray(); + } + + public static byte[] Serialize(ref ProbeRequestData probeRequestData) + { + var bytes = new List(); + BakeInputWriter writer = new(bytes); + + UInt64 fileVersion = CurrentFileVersion; + writer.TransferBlittable(ref fileVersion); + + writer.Transfer(ref probeRequestData); + + return bytes.ToArray(); + } + + public static void Serialize(string path, ref BakeInput bakeInput) + { + File.WriteAllBytes(path, Serialize(ref bakeInput)); + } + + public static void Serialize(string path, ref LightmapRequestData lightmapRequestData) + { + File.WriteAllBytes(path, Serialize(ref lightmapRequestData)); + } + + public static void Serialize(string path, ref ProbeRequestData probeRequestData) + { + File.WriteAllBytes(path, Serialize(ref probeRequestData)); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs.meta new file mode 100644 index 00000000000..0bed799d4a9 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputSerialization.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb279d035a8fb402a96ef7d3596f54c8 diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs new file mode 100644 index 00000000000..6b7085126aa --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs @@ -0,0 +1,529 @@ +using System; +using System.Collections.Generic; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.LightTransport; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering.Sampling; + +namespace UnityEditor.PathTracing.LightBakerBridge +{ + using MaterialHandle = Handle; + using LightHandle = Handle; + + internal static class BakeInputToWorldConversion + { + private static Mesh MeshDataToMesh(in MeshData meshData) + { + ref readonly VertexData vertexData = ref meshData.vertexData; + + var outMesh = new Mesh(); + var outRawMeshArray = Mesh.AllocateWritableMeshData(1); + var outRawMesh = outRawMeshArray[0]; + + int vertexCount = (int)vertexData.vertexCount; + List vertexLayout = new(); + if (vertexData.meshShaderChannelMask.HasFlag(MeshShaderChannelMask.Vertex)) + vertexLayout.Add(new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3)); + if (vertexData.meshShaderChannelMask.HasFlag(MeshShaderChannelMask.Normal)) + vertexLayout.Add(new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3)); + if (vertexData.meshShaderChannelMask.HasFlag(MeshShaderChannelMask.TexCoord0)) + vertexLayout.Add(new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2)); + if (vertexData.meshShaderChannelMask.HasFlag(MeshShaderChannelMask.TexCoord1)) + vertexLayout.Add(new VertexAttributeDescriptor(VertexAttribute.TexCoord1, VertexAttributeFormat.Float32, 2)); + outRawMesh.SetVertexBufferParams(vertexCount, vertexLayout.ToArray()); + outRawMesh.GetVertexData().CopyFrom(vertexData.data); + + outRawMesh.SetIndexBufferParams(meshData.indexBuffer.Length, IndexFormat.UInt32); + outRawMesh.GetIndexData().CopyFrom(meshData.indexBuffer); + + int subMeshCount = meshData.subMeshAABB.Length; + outRawMesh.subMeshCount = subMeshCount; + for (int sm = 0; sm < outRawMesh.subMeshCount; sm++) + { + var smd = new SubMeshDescriptor((int)meshData.subMeshIndexOffset[sm], (int)meshData.subMeshIndexCount[sm]); + outRawMesh.SetSubMesh(sm, smd); + } + + Mesh.ApplyAndDisposeWritableMeshData(outRawMeshArray, outMesh); + outMesh.RecalculateBounds(); + + // MeshData from LightBaker contains UVs that are scaled to perfectly fit in the [0, 1] range. + // The scale and offset used to achieve that are stored in the uvScaleOffset field. + // Here we undo the scaling to get the original UVs of the input mesh. + Vector2[] uv2 = outMesh.uv2; + float4 uvScaleOffset = meshData.uvScaleOffset; + Vector2 uvScale = new Vector2(uvScaleOffset.x, uvScaleOffset.y); + Vector2 uvOffset = new Vector2(uvScaleOffset.z, uvScaleOffset.w); + for (int i = 0; i < uv2.Length; i++) + { + uv2[i] = (uv2[i] - uvOffset) / uvScale; + } + outMesh.uv2 = uv2; + + return outMesh; + } + + private static Texture2D CreateTexture2DFromTextureData(in TextureData textureData, string name = "CreateTexture2DFromTextureData") + { + Texture2D texture = new Texture2D((int)textureData.width, (int)textureData.height, TextureFormat.RGBAFloat, false, linear: true) { name = name }; + texture.SetPixelData(textureData.data, 0); + texture.Apply(false, false); + texture.wrapMode = TextureWrapMode.Clamp; + return texture; + } + + private static Texture CreateTextureFromCookieData(in CookieData textureData) + { + if (textureData.slices == 1) + { + Texture2D texture = new Texture2D((int)textureData.width, (int)textureData.height, TextureFormat.RGBA32, false, linear: true); + texture.SetPixelData(textureData.textureData, 0); + texture.Apply(false, false); + return texture; + + } + else + { + Cubemap texture = new Cubemap((int)textureData.width, TextureFormat.RGBA32, false); + uint faceStride = textureData.width * textureData.width * textureData.pixelStride; + + for (int faceIndex = 0; faceIndex < textureData.slices; faceIndex++) + texture.SetPixelData(textureData.textureData, 0, (CubemapFace)faceIndex, faceIndex * (int)faceStride); + + texture.Apply(false, false); + return texture; + } + } + + private static UnityEngine.LightType LightBakerLightTypeToUnityLightType(LightBakerBridge.LightType type) + { + switch (type) + { + case LightBakerBridge.LightType.Directional: return UnityEngine.LightType.Directional; + case LightBakerBridge.LightType.Point: return UnityEngine.LightType.Point; + case LightBakerBridge.LightType.Spot: return UnityEngine.LightType.Spot; + case LightBakerBridge.LightType.Rectangle: return UnityEngine.LightType.Rectangle; + case LightBakerBridge.LightType.Disc: return UnityEngine.LightType.Disc; + case LightBakerBridge.LightType.SpotBoxShape: return UnityEngine.LightType.Box; + default: throw new ArgumentException("Unknown light type"); + } + } + + private static UnityEngine.Experimental.GlobalIllumination.FalloffType LightBakerFalloffTypeToUnityFalloffType(FalloffType falloff) + { + switch (falloff) + { + case FalloffType.InverseSquared: + return UnityEngine.Experimental.GlobalIllumination.FalloffType.InverseSquared; + case FalloffType.InverseSquaredNoRangeAttenuation: + return UnityEngine.Experimental.GlobalIllumination.FalloffType.InverseSquaredNoRangeAttenuation; + case FalloffType.Linear: + return UnityEngine.Experimental.GlobalIllumination.FalloffType.Linear; + case FalloffType.Legacy: + return UnityEngine.Experimental.GlobalIllumination.FalloffType.Legacy; + case FalloffType.None: + return UnityEngine.Experimental.GlobalIllumination.FalloffType.Undefined; + default: + Debug.Assert(false, $"Unknown falloff type: {falloff}"); + return UnityEngine.Experimental.GlobalIllumination.FalloffType.Undefined; + } + } + + internal static void InjectAnalyticalLights( + World world, + bool autoEstimateLUTRange, + in BakeInput bakeInput, + out LightHandle[] lightHandles, + List allocatedObjects) + { + // Extract lights + var lights = new World.LightDescriptor[bakeInput.lightData.Length]; + for (int i = 0; i < bakeInput.lightData.Length; i++) + { + ref readonly LightData lightData = ref bakeInput.lightData[i]; + + // TODO(pema.malling): The following transform is only correct for linear color space :( https://jira.unity3d.com/browse/LIGHT-1763 + float maxColor = Mathf.Max(lightData.color.x, Mathf.Max(lightData.color.y, lightData.color.z)); + float maxIndirectColor = Mathf.Max(lightData.indirectColor.x, Mathf.Max(lightData.indirectColor.y, lightData.indirectColor.z)); + float bounceIntensity = maxColor <= 0 ? 0 : maxIndirectColor / maxColor; + + World.LightDescriptor lightDescriptor; + lightDescriptor.Type = LightBakerLightTypeToUnityLightType(lightData.type); + // We multiply intensity by PI, since LightBaker produces radiance estimates that are too bright by a factor of PI, + // for light coming from punctual light sources. This isn't correct, but we need to match LightBaker's output. + // Instead of adding incorrect code to the baker itself, we do the multiplication on the outside. + float3 linearColor = lightData.color; + lightDescriptor.LinearLightColor = linearColor; + lightDescriptor.Shadows = lightData.castsShadows ? LightShadows.Hard : LightShadows.None; + lightDescriptor.Transform = Matrix4x4.TRS(lightData.position, lightData.orientation, Vector3.one); + lightDescriptor.ColorTemperature = 0; + lightDescriptor.LightmapBakeType = lightData.mode == LightMode.Mixed ? LightmapBakeType.Mixed : LightmapBakeType.Baked; + lightDescriptor.AreaSize = Vector2.one; + lightDescriptor.SpotAngle = 0; + lightDescriptor.InnerSpotAngle = 0; + lightDescriptor.CullingMask = uint.MaxValue; + lightDescriptor.BounceIntensity = bounceIntensity; + lightDescriptor.Range = lightData.range; + lightDescriptor.ShadowMaskChannel = (lightData.shadowMaskChannel < 4) ? (int)lightData.shadowMaskChannel : -1; + lightDescriptor.UseColorTemperature = false; + lightDescriptor.FalloffType = LightBakerFalloffTypeToUnityFalloffType(lightData.falloff); + lightDescriptor.ShadowRadius = Util.IsPunctualLightType(lightDescriptor.Type) ? lightData.shape0 : 0.0f; + lightDescriptor.CookieSize = lightData.cookieScale; + lightDescriptor.CookieTexture = Util.IsCookieValid(lightData.cookieTextureIndex) ? CreateTextureFromCookieData(in bakeInput.cookieData[lightData.cookieTextureIndex]) : null; + if (lightDescriptor.CookieTexture != null) + allocatedObjects.Add(lightDescriptor.CookieTexture); + + switch (lightDescriptor.Type) + { + case UnityEngine.LightType.Box: + case UnityEngine.LightType.Rectangle: + lightDescriptor.AreaSize = new Vector2(lightData.shape0, lightData.shape1); + break; + + case UnityEngine.LightType.Disc: + lightDescriptor.AreaSize = new Vector2(lightData.shape0, lightData.shape0); + break; + + case UnityEngine.LightType.Spot: + lightDescriptor.SpotAngle = Mathf.Rad2Deg * lightData.coneAngle; + // TODO(pema.malling): This isn't quite correct, but very close. I couldn't figure out the math. See ExtractInnerCone(). https://jira.unity3d.com/browse/LIGHT-1727 + lightDescriptor.InnerSpotAngle = Mathf.Rad2Deg * lightData.innerConeAngle; + break; + + case UnityEngine.LightType.Directional: + lightDescriptor.AreaSize = new Vector2(lightData.coneAngle, lightData.innerConeAngle); + break; + } + + lights[i] = lightDescriptor; + } + + world.lightPickingMethod = LightPickingMethod.LightGrid; + lightHandles = world.AddLights(lights, false, autoEstimateLUTRange, bakeInput.lightingSettings.mixedLightingMode); + } + + internal static void InjectEnvironmentLight( + World world, + in BakeInput bakeInput, + List allocatedObjects) + { + // Setup environment light + int envCubemapResolution = (int)bakeInput.environmentData.cubeResolution; + var envCubemap = new Cubemap(envCubemapResolution, TextureFormat.RGBAFloat, false); + bool isEmptyCubemap = envCubemapResolution == 1; + for (int i = 0; i < 6; i++) + { + envCubemap.SetPixelData(bakeInput.environmentData.cubeData, 0, (CubemapFace)i, envCubemapResolution * envCubemapResolution * i); + isEmptyCubemap &= math.all(bakeInput.environmentData.cubeData[i].xyz == float3.zero); + } + + envCubemap.Apply(); + allocatedObjects.Add(envCubemap); + // If we have no cubemap (i.e. the 1x1x1 black texture), don't set it - we don't want to waste samples directly sampling it. + if (!isEmptyCubemap) + { + var envCubemapMaterial = new Material(Shader.Find("Hidden/PassthroughSkybox")); + envCubemapMaterial.SetTexture("_Tex", envCubemap); + world.SetEnvironmentMaterial(envCubemapMaterial); + allocatedObjects.Add(envCubemapMaterial); + } + } + + internal static void InjectMaterials( + World world, + in BakeInput bakeInput, + out MaterialHandle[][] perInstanceSubMeshMaterials, + out bool[][] perInstanceSubMeshVisibility, + List allocatedObjects) + { + int allocationCount = allocatedObjects.Count; + + // Create albedo and emission textures from materials + var perTexturePairMaterials = new World.MaterialDescriptor[bakeInput.albedoData.Length]; + Debug.Assert(bakeInput.albedoData.Length == bakeInput.emissiveData.Length); + for (int i = 0; i < bakeInput.albedoData.Length; i++) + { + ref var material = ref perTexturePairMaterials[i]; + var baseTexture = CreateTexture2DFromTextureData(in bakeInput.albedoData[i], $"World (albedo) {i}"); + allocatedObjects.Add(baseTexture); + var emissiveTexture = CreateTexture2DFromTextureData(in bakeInput.emissiveData[i], $"World (emissive) {i}"); + allocatedObjects.Add(emissiveTexture); + material.Albedo = baseTexture; + material.Emission = emissiveTexture; + + // Only mark emissive if it isn't the default black texture + bool isEmissiveSinglePixel = bakeInput.emissiveData[i].data.Length == 1; + bool isEmissiveBlack = math.all(bakeInput.emissiveData[i].data[0].xyz == float3.zero); + if (isEmissiveSinglePixel && isEmissiveBlack) + { + material.EmissionType = UnityEngine.PathTracing.Core.MaterialPropertyType.None; + material.EmissionColor = Vector3.zero; + } + else + { + material.EmissionType = UnityEngine.PathTracing.Core.MaterialPropertyType.Texture; + material.EmissionColor = Vector3.one; + } + + perTexturePairMaterials[i] = material; + } + + // Create all the unique transmission textures in bakeInput.transmissionData. + Texture2D[] transmissiveTextures = new Texture2D[bakeInput.transmissionData.Length]; + for (int i = 0; i < bakeInput.transmissionData.Length; i++) + { + ref readonly TextureData transmissionData = ref bakeInput.transmissionData[i]; + ref readonly TextureProperties transmissionDataProperties = ref bakeInput.transmissionDataProperties[i]; + Texture2D transmissiveTexture = CreateTexture2DFromTextureData(in transmissionData, $"World (transmission) {i}"); + transmissiveTexture.wrapModeU = transmissionDataProperties.wrapModeU; + transmissiveTexture.wrapModeV = transmissionDataProperties.wrapModeV; + transmissiveTexture.filterMode = transmissionDataProperties.filterMode; + allocatedObjects.Add(transmissiveTexture); + transmissiveTextures[i] = transmissiveTexture; + } + + // Certain material properties we can only determine by looking at individual submeshes of each instance. + // Therefore, we must make a copy of the base material for each submesh. We create these materials here. + perInstanceSubMeshMaterials = new MaterialHandle[bakeInput.instanceData.Length][]; + perInstanceSubMeshVisibility = new bool[bakeInput.instanceData.Length][]; + // To avoid needlessly creating duplicate materials, we also cache the materials we've already created: + // Hashing texturePairIdx handles deduplicating by source mesh, scale and the set of source materials. We hash materialIdx + // to identify the specific source material in the set associated with the texture pair (in case there are submeshes). + Dictionary<(uint texturePairIdx, int materialIdx), MaterialHandle> materialCache = new(); + for (int instanceIdx = 0; instanceIdx < bakeInput.instanceData.Length; instanceIdx++) + { + // Get base (per-instance) material + ref readonly InstanceData instanceData = ref bakeInput.instanceData[instanceIdx]; + uint texturePairIdx = bakeInput.instanceToTextureDataIndex[instanceIdx]; + ref readonly World.MaterialDescriptor baseMaterial = ref perTexturePairMaterials[texturePairIdx]; + + // Make space for per-submesh materials and visibility + perInstanceSubMeshMaterials[instanceIdx] = new MaterialHandle[instanceData.subMeshMaterialIndices.Length]; + perInstanceSubMeshVisibility[instanceIdx] = new bool[instanceData.subMeshMaterialIndices.Length]; + + // Extract per-subMesh materials + for (int subMeshIdx = 0; subMeshIdx < instanceData.subMeshMaterialIndices.Length; subMeshIdx++) + { + int subMeshMaterialIdx = instanceData.subMeshMaterialIndices[subMeshIdx]; + + // If we've already created this material, use it + if (materialCache.TryGetValue((texturePairIdx, subMeshMaterialIdx), out MaterialHandle existingHandle)) + { + perInstanceSubMeshMaterials[instanceIdx][subMeshIdx] = existingHandle; + perInstanceSubMeshVisibility[instanceIdx][subMeshIdx] = true; + continue; + } + + // Copy the base material + World.MaterialDescriptor subMeshMaterial = baseMaterial; + + // Get per-subMesh material properties, set them on the copy + if (-1 != subMeshMaterialIdx) + { + ref readonly MaterialData materialData = ref bakeInput.materialData[subMeshMaterialIdx]; + subMeshMaterial.DoubleSidedGI = materialData.doubleSidedGI; + + // Set transmission texture, if any + int transmissionDataIndex = bakeInput.materialToTransmissionDataIndex[subMeshMaterialIdx]; + if (-1 != transmissionDataIndex) + { + subMeshMaterial.Transmission = transmissiveTextures[transmissionDataIndex]; + ref readonly TextureProperties transmissionDataProperties = ref bakeInput.transmissionDataProperties[transmissionDataIndex]; + subMeshMaterial.TransmissionScale = transmissionDataProperties.transmissionTextureST.scale; + subMeshMaterial.TransmissionOffset = transmissionDataProperties.transmissionTextureST.offset; + subMeshMaterial.TransmissionChannels = UnityEngine.PathTracing.Core.TransmissionChannels.RGB; + subMeshMaterial.PointSampleTransmission = transmissionDataProperties.filterMode == FilterMode.Point; + } + + // Apply the stretching operation that LightBaker applies - ensures that the UV layout fills the entire UV space + if (instanceData.meshIndex >= 0) + { + Vector4 uvScaleOffset = bakeInput.meshData[instanceData.meshIndex].uvScaleOffset; + Vector2 uvScale = new Vector2(uvScaleOffset.x, uvScaleOffset.y); + Vector2 uvOffset = new Vector2(uvScaleOffset.z, uvScaleOffset.w); + subMeshMaterial.AlbedoScale = uvScale; + subMeshMaterial.AlbedoOffset = uvOffset; + subMeshMaterial.EmissionScale = uvScale; + subMeshMaterial.EmissionOffset = uvOffset; + } + else + { + subMeshMaterial.AlbedoScale = Vector2.one; + subMeshMaterial.AlbedoOffset = Vector2.zero; + subMeshMaterial.EmissionScale = Vector2.one; + subMeshMaterial.EmissionOffset = Vector2.zero; + } + } + + MaterialHandle addedHandle = world.AddMaterial(in subMeshMaterial, UVChannel.UV1); + materialCache.Add((texturePairIdx, subMeshMaterialIdx), addedHandle); + perInstanceSubMeshMaterials[instanceIdx][subMeshIdx] = addedHandle; + perInstanceSubMeshVisibility[instanceIdx][subMeshIdx] = subMeshMaterialIdx != -1; + } + } + Debug.Assert(allocatedObjects.Count == allocationCount + bakeInput.albedoData.Length * 2 + bakeInput.transmissionData.Length, "InjectMaterials allocated objects incorrectly"); + } + + internal static void ConvertInstancesAndMeshes( + World world, + in BakeInput bakeInput, + in MaterialHandle[][] perInstanceSubMeshMaterials, + in bool[][] perInstanceSubMeshVisibility, + out Bounds sceneBounds, + out Mesh[] meshes, + out FatInstance[] fatInstances, + List allocatedObjects, + uint renderingObjectLayer) + { + sceneBounds = new Bounds(); + + // Extract meshes + meshes = new Mesh[bakeInput.meshData.Length]; + int meshIndex = 0; + for (int i = 0; i < bakeInput.meshData.Length; i++) + { + meshes[meshIndex] = MeshDataToMesh(in bakeInput.meshData[meshIndex]); + meshes[meshIndex].name = $"{meshIndex}"; + meshIndex++; + } + + // Compute the tight UV scale and offset for each mesh. + Vector2[] uvBoundsSizes = new Vector2[meshes.Length]; + Vector2[] uvBoundsOffsets = new Vector2[meshes.Length]; + for (int i = 0; i < meshes.Length; ++i) + { + if (meshes[i].uv2.Length == 0) + LightmapIntegrationHelpers.ComputeUVBounds(meshes[i].uv, out uvBoundsSizes[i], out uvBoundsOffsets[i]); + else + LightmapIntegrationHelpers.ComputeUVBounds(meshes[i].uv2, out uvBoundsSizes[i], out uvBoundsOffsets[i]); + } + Debug.Assert(meshes.Length == bakeInput.meshData.Length); + + // Baking specific settings + RenderedGameObjectsFilter filter = RenderedGameObjectsFilter.OnlyStatic; + const bool isStatic = true; + + // Extract instances + List fatInstanceList = new(); + for (int i = 0; i < bakeInput.instanceData.Length; i++) + { + // Get materials + ref readonly InstanceData instanceData = ref bakeInput.instanceData[i]; + var materials = perInstanceSubMeshMaterials[i]; + var visibility = perInstanceSubMeshVisibility[i]; + + // Get other instance data + float4x4 localToWorldFloat4x4 = instanceData.transform; + Matrix4x4 localToWorldMatrix4x4 = new Matrix4x4(localToWorldFloat4x4.c0, localToWorldFloat4x4.c1, localToWorldFloat4x4.c2, localToWorldFloat4x4.c3); + ShadowCastingMode shadowCastingMode = instanceData.castShadows ? ShadowCastingMode.On : ShadowCastingMode.Off; + Debug.Assert(instanceData.meshIndex >= 0 && instanceData.meshIndex < meshes.Length); + Mesh mesh = meshes[instanceData.meshIndex]; + Vector2 uvBoundsSize = uvBoundsSizes[instanceData.meshIndex]; + Vector2 uvBoundsOffset = uvBoundsOffsets[instanceData.meshIndex]; + + // Calculate bounds + var bounds = new Bounds(); + foreach (Vector3 vert in mesh.vertices) + { + bounds.Encapsulate(localToWorldMatrix4x4.MultiplyPoint(vert)); // TODO: transform the bounding box instead of looping verts (https://jira.unity3d.com/browse/GFXFEAT-667) + } + + // Keep track of scene bounds as we go + if (i == 0) + sceneBounds = bounds; + else + sceneBounds.Encapsulate(bounds); + + // Get masks + uint[] subMeshMasks = new uint[mesh.subMeshCount]; + for (int s = 0; s < mesh.subMeshCount; ++s) + { + subMeshMasks[s] = visibility[s] ? World.GetInstanceMask(shadowCastingMode, isStatic, filter) : 0u; + } + + // add instance + var boundingSphere = new BoundingSphere(); + boundingSphere.position = localToWorldMatrix4x4.MultiplyPoint(mesh.bounds.center); + boundingSphere.radius = (localToWorldMatrix4x4.MultiplyPoint(mesh.bounds.extents) - boundingSphere.position).magnitude; + var lodIdentifier = new LodIdentifier(instanceData.lodGroup, instanceData.lodMask, 0); // TODO(pema.malling): Contributing lod level + var fatInstance = new FatInstance + { + BoundingSphere = boundingSphere, + Mesh = mesh, + UVBoundsSize = uvBoundsSize, + UVBoundsOffset = uvBoundsOffset, + Materials = materials, + SubMeshMasks = subMeshMasks, + LocalToWorldMatrix = localToWorldMatrix4x4, + Bounds = bounds, + IsStatic = isStatic, + LodIdentifier = lodIdentifier, + ReceiveShadows = instanceData.receiveShadows, + Filter = filter, + RenderingObjectLayer = renderingObjectLayer, + EnableEmissiveSampling = true + }; + fatInstanceList.Add(fatInstance); + } + fatInstances = fatInstanceList.ToArray(); + Debug.Assert(fatInstances.Length == bakeInput.instanceData.Length); + } + + internal static void PopulateWorld(InputExtraction.BakeInput input, UnityComputeWorld world, SamplingResources samplingResources, CommandBuffer cmd, bool autoEstimateLUTRange) + { + FatInstance[] fatInstances; + BakeInputToWorldConversion.DeserializeAndInjectBakeInputData(world.PathTracingWorld, autoEstimateLUTRange, in input, + out UnityEngine.Bounds sceneBounds, out world.Meshes, out fatInstances, out world.LightHandles, + world.TemporaryObjects, UnityComputeWorld.RenderingObjectLayer); + + // Add instances to world + Dictionary> lodInstances; + Dictionary> lodgroupToContributorInstances; + WorldHelpers.AddContributingInstancesToWorld(world.PathTracingWorld, in fatInstances, out lodInstances, out lodgroupToContributorInstances); + world.PathTracingWorld.Build(sceneBounds, cmd, ref world.ScratchBuffer, samplingResources, true); + } + + internal static void DeserializeAndInjectBakeInputData( + World world, + bool autoEstimateLUTRange, + in InputExtraction.BakeInput bakeInput, + out Bounds sceneBounds, + out Mesh[] meshes, + out FatInstance[] fatInstances, + out LightHandle[] lightHandles, + List allocatedObjects, + uint renderingObjectLayer) + { + string bakeInputPath = $"Temp/TempNative.bakeInput"; + bool serializeSucceeded = LightBaking.LightBaker.Serialize(bakeInputPath, bakeInput.bakeInput); + Debug.Assert(serializeSucceeded, $"Failed to serialize input to '{bakeInputPath}'."); + + LightBakerBridge.BakeInput conversionBakeInput; + bool deserializeSucceeded = BakeInputSerialization.Deserialize(bakeInputPath, out conversionBakeInput); + System.IO.File.Delete(bakeInputPath); + Debug.Assert(deserializeSucceeded, $"Failed to deserialize input from '{bakeInputPath}'."); + InjectBakeInputData(world, autoEstimateLUTRange, conversionBakeInput, out sceneBounds, out meshes, out fatInstances, out lightHandles, allocatedObjects, renderingObjectLayer); + } + + internal static void InjectBakeInputData( + World world, + bool autoEstimateLUTRange, + in BakeInput bakeInput, + out Bounds sceneBounds, + out Mesh[] meshes, + out FatInstance[] fatInstances, + out LightHandle[] lightHandles, + List allocatedObjects, + uint renderingObjectLayer) + { + InjectAnalyticalLights(world, autoEstimateLUTRange, bakeInput, out lightHandles, allocatedObjects); + InjectEnvironmentLight(world, bakeInput, allocatedObjects); + InjectMaterials(world, bakeInput, out var perInstanceSubMeshMaterials, out var perInstanceSubMeshVisibility, allocatedObjects); + ConvertInstancesAndMeshes(world, bakeInput, perInstanceSubMeshMaterials, perInstanceSubMeshVisibility, out sceneBounds, out meshes, out fatInstances, allocatedObjects, renderingObjectLayer); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs.meta new file mode 100644 index 00000000000..04b102a8371 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/BakeInputToWorldConversion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ac1148ae1e306490ca1665eba13b76b3 diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging.meta new file mode 100644 index 00000000000..0aff4bcaf2a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c4f17ac3ed79aaa4b8a042c89882ede7 diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs new file mode 100644 index 00000000000..66ac16764d0 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs @@ -0,0 +1,196 @@ +using System; +using UnityEngine; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering; + +namespace UnityEditor.PathTracing.Debugging +{ + internal static class AdaptiveSamplingDebugHelpers + { + internal static class ShaderIDs + { + public static readonly int TemporaryRT = Shader.PropertyToID("BakeLightmapDriverTemporaryRT"); + public static readonly int SecondTemporaryRT = Shader.PropertyToID("BakeLightmapDriverSecondTemporaryRT"); + } + + static internal void LogTotalSamplesTaken(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput, uint maxSampleCount) + { + int width = accumulatedOutput.width; + int height = accumulatedOutput.height; + UInt64 totalSampleSum = GetSampleCountSum(cmd, helpers, accumulatedOutput); + UInt64 maxSampleSum = (UInt64)width * (UInt64)height * (UInt64)maxSampleCount; + Debug.Log($"total samples taken:\t{totalSampleSum}\tout of: \t{maxSampleSum}\tfraction:\t{(double)totalSampleSum / (double)maxSampleSum}"); + } + + static internal LightmapIntegrationHelpers.AdaptiveSample GetAdaptiveSample(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput, RenderTexture adaptiveOutput, int sampleX, int sampleY) + { + Debug.Assert(accumulatedOutput.width == adaptiveOutput.width && accumulatedOutput.height == adaptiveOutput.height); + var width = adaptiveOutput.width; + var height = adaptiveOutput.height; + + Color accumulatedOutputValue = LightmapIntegrationHelpers.GetValue(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.GetValueKernel, sampleX, sampleY, width, height, accumulatedOutput); + float accumulatedLuminance = LightmapIntegrationHelpers.Luminance(accumulatedOutputValue); + Color adaptiveOutputValue = LightmapIntegrationHelpers.GetValue(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.GetValueKernel, sampleX, sampleY, width, height, adaptiveOutput); + + LightmapIntegrationHelpers.AdaptiveSample sample = new LightmapIntegrationHelpers.AdaptiveSample(); + sample.sampleCount = (uint)accumulatedOutputValue.a; + sample.accumulatedLuminance = accumulatedLuminance; + sample.mean = adaptiveOutputValue.r; + sample.meanSqr = adaptiveOutputValue.g; + sample.variance = adaptiveOutputValue.b; + sample.standardError = adaptiveOutputValue.a; + + // missing + sample.varianceFiltered = 0.0f;// LightmapIntegrationHelpers.GetValue(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.GetValueKernel, sampleX, sampleY, width, height, lightmappingContext.AdaptiveOutput).b; + sample.active = false;// LightmapIntegrationHelpers.GetValue(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.GetValueKernel, sampleX, sampleY, width, height, secondTemporaryRT).r > 0.5f; + + return sample; + } + + static internal void WriteAdaptiveDebugImages(string path, string filenamePostfix, CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput, RenderTexture adaptiveOutput, + uint totalSampleCount, float varianceScale, float errorScale, float adaptiveThreshold) + { + WriteIrradiance(cmd, helpers, accumulatedOutput, $"{path}/irradiance_{filenamePostfix}.r2d"); + WriteSampleCount(cmd, helpers, accumulatedOutput, totalSampleCount, $"{path}/sampleCount_{filenamePostfix}.r2d"); + WriteVariance(cmd, helpers, adaptiveOutput, varianceScale, $"{path}/variance_{filenamePostfix}.r2d"); + // this may be double filtered in converged parts of the lightmap + WriteFilteredVariance(cmd, helpers, adaptiveOutput, varianceScale, $"{path}/varianceFiltered_{filenamePostfix}.r2d"); + WriteStandardError(cmd, helpers, adaptiveOutput, errorScale, $"{path}/standardError_{filenamePostfix}.r2d"); + WriteActiveTexels(cmd, helpers, adaptiveOutput, adaptiveThreshold, $"{path}/active_{filenamePostfix}.r2d"); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, $"{path}/adaptive_{filenamePostfix}.r2d", adaptiveOutput); + } + + static void WriteIrradiance(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput, string filename) + { + int width = accumulatedOutput.width; + int height = accumulatedOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(accumulatedOutput, tempRTID); + LightmapIntegrationHelpers.NormalizeByAlpha(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.NormalizeByAlphaKernel, width, height, tempRTID); + LightmapIntegrationHelpers.SetChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.SetChannelKernel, tempRTID, width, height, 3, 1.0f); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + + cmd.ReleaseTemporaryRT(tempRTID); + } + + static void WriteSampleCount(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput, uint maxSampleCount, string filename) + { + int width = accumulatedOutput.width; + int height = accumulatedOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(accumulatedOutput, tempRTID); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, tempRTID, width, height, 3); + LightmapIntegrationHelpers.SetChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.SetChannelKernel, tempRTID, width, height, 3, 1.0f); + float samplesScale = 1.0f / maxSampleCount; + LightmapIntegrationHelpers.MultiplyRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.MultiplyKernel, tempRTID, width, height, new Vector4(samplesScale, samplesScale, samplesScale, 1.0f)); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + + cmd.ReleaseTemporaryRT(tempRTID); + } + + static void WriteVariance(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture adaptiveOutput, float scale, string filename) + { + int width = adaptiveOutput.width; + int height = adaptiveOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(adaptiveOutput, tempRTID); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, tempRTID, width, height, 2); + LightmapIntegrationHelpers.SetChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.SetChannelKernel, tempRTID, width, height, 3, 1.0f); + LightmapIntegrationHelpers.MultiplyRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.MultiplyKernel, tempRTID, width, height, new Vector4(scale, scale, scale, 1.0f)); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + + cmd.ReleaseTemporaryRT(tempRTID); + } + + static void WriteFilteredVariance(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture adaptiveOutput, float scale, string filename) + { + int width = adaptiveOutput.width; + int height = adaptiveOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(adaptiveOutput, tempRTID); + LightmapIntegrationHelpers.ReferenceBoxFilterRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.ReferenceBoxFilterBlueChannelKernel, tempRTID, width, height, 2); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, tempRTID, width, height, 2); + LightmapIntegrationHelpers.SetChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.SetChannelKernel, tempRTID, width, height, 3, 1.0f); + LightmapIntegrationHelpers.MultiplyRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.MultiplyKernel, tempRTID, width, height, new Vector4(scale, scale, scale, 1.0f)); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + + cmd.ReleaseTemporaryRT(tempRTID); + } + + static void WriteStandardError(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture adaptiveOutput, float scale, string filename) + { + int width = adaptiveOutput.width; + int height = adaptiveOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(adaptiveOutput, tempRTID); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, tempRTID, width, height, 3); + LightmapIntegrationHelpers.SetChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.SetChannelKernel, tempRTID, width, height, 3, 1.0f); + LightmapIntegrationHelpers.MultiplyRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.MultiplyKernel, tempRTID, width, height, new Vector4(scale, scale, scale, 1.0f)); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + + cmd.ReleaseTemporaryRT(tempRTID); + } + + static int GetActiveTexelImage(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture adaptiveOutput, float adaptiveThreshold) + { + int width = adaptiveOutput.width; + int height = adaptiveOutput.height; + + int tempRTID = ShaderIDs.TemporaryRT; + int secondTempRTID = ShaderIDs.SecondTemporaryRT; + cmd.GetTemporaryRT(secondTempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + cmd.GetTemporaryRT(tempRTID, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + + cmd.CopyTexture(adaptiveOutput, tempRTID); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, tempRTID, width, height, 3); + LightmapIntegrationHelpers.StandardErrorThresholdRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.StandardErrorThresholdKernel, tempRTID, adaptiveOutput, adaptiveThreshold, secondTempRTID, width, height); + cmd.ReleaseTemporaryRT(tempRTID); + + return secondTempRTID; + } + + static void WriteActiveTexels(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture adaptiveOutput, float adaptiveThreshold, string filename) + { + int width = adaptiveOutput.width; + int height = adaptiveOutput.height; + int tempRTID = GetActiveTexelImage(cmd, helpers, adaptiveOutput, adaptiveThreshold); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, tempRTID, TextureFormat.RGBAFloat, width, height, filename); + cmd.ReleaseTemporaryRT(tempRTID); + } + + static UInt64 GetSampleCountSum(CommandBuffer cmd, LightmapIntegrationHelpers.ComputeHelpers helpers, RenderTexture accumulatedOutput) + { + int width = accumulatedOutput.width; + int height = accumulatedOutput.height; + + var temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear) + { name = "temporaryRT (GetSampleCountSum)", enableRandomWrite = true, hideFlags = HideFlags.HideAndDontSave }; + temporaryRT.Create(); + + cmd.CopyTexture(accumulatedOutput, temporaryRT); + LightmapIntegrationHelpers.BroadcastChannelRenderTexture(cmd, helpers.ComputeHelperShader, LightmapIntegrationHelpers.ComputeHelpers.BroadcastChannelKernel, temporaryRT, width, height, 3); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Clear(); + double totalSampleSum = LightmapIntegrationHelpers.GetSum(width, height, temporaryRT).x; + + temporaryRT.Release(); + CoreUtils.Destroy(temporaryRT); + return (UInt64)totalSampleSum; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs.meta new file mode 100644 index 00000000000..0bd19af6dd6 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/AdaptiveSamplingDebugHelpers.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d45701e61be7ef9449716f810d3d7410 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs new file mode 100644 index 00000000000..8dbd1b48f4c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs @@ -0,0 +1,43 @@ +using System; +using UnityEditor; +using UnityEngine.Experimental.Rendering; + +namespace UnityEditor.PathTracing.Debugging +{ + internal sealed class BakeProfilingScope : IDisposable + { + private readonly bool _shouldCapture; + + private static bool IsFullBakeCaptureEnabled() + { + if (!Unsupported.IsDeveloperMode()) + return false; + + var prefSetting = new SavedBool("LightingSettings.CaptureBakeForProfiling", false); + return prefSetting.value; + } + + public BakeProfilingScope(bool shouldCapture) + { + _shouldCapture = shouldCapture; + + if (_shouldCapture) + { + ExternalGPUProfiler.BeginGPUCapture(); + } + } + + public BakeProfilingScope() + : this(IsFullBakeCaptureEnabled()) + { + } + + public void Dispose() + { + if (_shouldCapture) + { + ExternalGPUProfiler.EndGPUCapture(); + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs.meta new file mode 100644 index 00000000000..9c83350532f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Debugging/BakeDebugHelpers.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 88ef5b3921c501743a28218f620b982a diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs new file mode 100644 index 00000000000..23a52710696 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs @@ -0,0 +1,1679 @@ +using System.Collections.Generic; +using System.IO; +using System; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.LightTransport; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering; +using UnityEngine.Assertions; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering.UnifiedRayTracing; +using Unity.Collections.LowLevel.Unsafe; +using System.Runtime.InteropServices; +using UnityEditor.PathTracing.Debugging; + +namespace UnityEditor.PathTracing.LightBakerBridge +{ + using static BakeLightmapDriver; + using InstanceHandle = Handle; + + internal static class WorldHelpers + { + static void MultiValueDictAdd(Dictionary> dict, TKey key, TValue value) + { + List values = null; + if (dict.TryGetValue(key, out values)) + { + values.Add(value); + } + else + { + values = new List { value }; + dict.Add(key, values); + } + } + + internal static void AddContributingInstancesToWorld(World world, in FatInstance[] fatInstances, out Dictionary> lodInstances, out Dictionary> lodgroupToContributorInstances) + { + lodInstances = new(); + lodgroupToContributorInstances = new(); + // Add instances to world + foreach (var fatInstance in fatInstances) + { + if (fatInstance.LodIdentifier.IsValid() && !fatInstance.LodIdentifier.IsContributor()) + { + WorldHelpers.MultiValueDictAdd(lodInstances, fatInstance.LodIdentifier.LodGroup, new LodInstanceBuildData + { + LodMask = fatInstance.LodIdentifier.LodMask, + Mesh = fatInstance.Mesh, + Materials = fatInstance.Materials, + Masks = fatInstance.SubMeshMasks, + LocalToWorldMatrix = fatInstance.LocalToWorldMatrix, + Bounds = fatInstance.Bounds, + IsStatic = fatInstance.IsStatic, + Filter = fatInstance.Filter + }); + continue; + } + + var instanceHandle = world.AddInstance( + fatInstance.Mesh, + fatInstance.Materials, + fatInstance.SubMeshMasks, + fatInstance.RenderingObjectLayer, + fatInstance.LocalToWorldMatrix, + fatInstance.Bounds, + fatInstance.IsStatic, + fatInstance.Filter, + fatInstance.EnableEmissiveSampling); + + if (fatInstance.LodIdentifier.IsValid() && fatInstance.LodIdentifier.IsContributor()) + WorldHelpers.MultiValueDictAdd(lodgroupToContributorInstances, fatInstance.LodIdentifier.LodGroup, new ContributorLodInfo + { + LodMask = fatInstance.LodIdentifier.LodMask, + InstanceHandle = instanceHandle, + Masks = fatInstance.SubMeshMasks + }); + } + } + } + + [InitializeOnLoad] + internal class SetLightmappingUnifiedBaker + { + static SetLightmappingUnifiedBaker() + { + try + { + var lightmappingType = typeof(UnityEditor.Lightmapping); + var unifiedBakerProperty = lightmappingType.GetProperty("UnifiedBaker", + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + + if (unifiedBakerProperty != null && unifiedBakerProperty.CanWrite) + { + #if UNIFIED_BAKER + unifiedBakerProperty.SetValue(null, true); + #else + unifiedBakerProperty.SetValue(null, false); + #endif + } + else + { + UnityEngine.Debug.LogWarning("Could not find or access UnifiedBaker property on Lightmapping class"); + } + } + catch (System.Exception ex) + { + UnityEngine.Debug.LogError($"Failed to set UnifiedBaker property via reflection: {ex.Message}"); + } + } + } + + internal class LightBakerStrangler + { + internal enum Result + { + Success, + InitializeFailure, + CreateDirectoryFailure, + CreateLightmapFailure, + AddResourcesToCacheFailure, + InitializeExpandedBufferFailure, + WriteToDiskFailure, + Cancelled + }; + + // Mirrors native side LightProbeOcclusion struct + [StructLayout(LayoutKind.Sequential)] + public unsafe struct LightProbeOcclusion + { + public const int MaxLightsPerProbe = 4; + + public fixed int ProbeOcclusionLightIndex[MaxLightsPerProbe]; + public fixed float Occlusion[MaxLightsPerProbe]; + public fixed sbyte OcclusionMaskChannel[MaxLightsPerProbe]; + + public int GetProbeOcclusionLightIndex(int index) => ProbeOcclusionLightIndex[index]; + public void SetProbeOcclusionLightIndex(int index, int value) => ProbeOcclusionLightIndex[index] = value; + public float GetOcclusion(int index) => Occlusion[index]; + public void SetOcclusion(int index, float value) => Occlusion[index] = value; + public sbyte GetOcclusionMaskChannel(int index) => OcclusionMaskChannel[index]; + public void SetOcclusionMaskChannel(int index, sbyte value) => OcclusionMaskChannel[index] = value; + + public void SetDefaultValues() + { + for (int i = 0; i < MaxLightsPerProbe; ++i) + { + ProbeOcclusionLightIndex[i] = -1; + Occlusion[i] = 0.0f; + OcclusionMaskChannel[i] = -1; + } + } + } + + // File layout matches WriteInterleavedSHArrayToFile. + private static bool SaveProbesToFileInterleaved(string filename, NativeArray shArray) + { + Debug.Assert(filename is not null, "Filename is null"); + + string path = Path.GetDirectoryName(filename); + Debug.Assert(path is not null, "path is null"); + var info = Directory.CreateDirectory(path); + if (info.Exists == false) + return false; + + // Write the number of probes to file as Int64. + Int64 arraySize = shArray.Length; + byte[] probeCountBytes = BitConverter.GetBytes(arraySize); + List byteList = new(); + byteList.AddRange(probeCountBytes); + + // Write all probe coefficients, ordered by coefficient. + const int sphericalHarmonicsL2CoeffCount = 9; + byte[] floatPaddingBytes = BitConverter.GetBytes(0.0f); + for (int coefficient = 0; coefficient < sphericalHarmonicsL2CoeffCount; ++coefficient) + { + for (int i = 0; i < shArray.Length; i++) + { + SphericalHarmonicsL2 sh = shArray[i]; + for (int rgb = 0; rgb < 3; rgb++) + { + float coefficientValue = sh[rgb, coefficient]; + byte[] floatBytes = BitConverter.GetBytes(coefficientValue); + byteList.AddRange(floatBytes); + } + byteList.AddRange(floatPaddingBytes); // pad to match Vector4 size. + } + } + File.WriteAllBytes(filename, byteList.ToArray()); + return true; + } + + private static bool SaveArrayToFile(string filename, int count, T[] array) + where T : unmanaged + { + // Create output directory if it doesn't exist already. + Debug.Assert(filename is not null, "Filename is null"); + string path = Path.GetDirectoryName(filename); + Debug.Assert(path is not null, "path is null"); + var info = Directory.CreateDirectory(path); + if (info.Exists == false) + return false; + + // Prepare output buffer. + int numElementBytes = array.Length * UnsafeUtility.SizeOf(); + byte[] bytes = new byte[sizeof(Int64) + numElementBytes]; // + sizeof(Int64) for the count. + + // Write the number of elements to file as Int64. + Int64 arraySize = count; + byte[] countBytes = BitConverter.GetBytes(arraySize); + Buffer.BlockCopy(countBytes, 0, bytes, 0, countBytes.Length); + + // Write contents of array to file. This is safe because of the unmanaged constraint on T. + unsafe + { + fixed (T* arrayPtr = array) + fixed (byte* bytesPtr = bytes) + { + UnsafeUtility.MemCpy(bytesPtr + sizeof(Int64), arrayPtr, numElementBytes); + } + } + File.WriteAllBytes(filename, bytes); + return true; + } + + private static HashSet BitfieldToList(int bitfield) + { + var outputTypes = new HashSet(); + + // Loop through the enum values + foreach (LightmapRequestOutputType channel in Enum.GetValues(typeof(LightmapRequestOutputType))) + { + // Check if the bitfield has the current value set + if ((bitfield & (int) channel) == 0) + continue; + // Add the value to the list + if (channel != LightmapRequestOutputType.All) + outputTypes.Add(channel); + } + return outputTypes; + } + + private static bool LightmapRequestOutputTypeToIntegratedOutputType(LightmapRequestOutputType type, out IntegratedOutputType integratedOutputType) + { + integratedOutputType = IntegratedOutputType.AO; + switch (type) + { + case LightmapRequestOutputType.IrradianceIndirect: + integratedOutputType = IntegratedOutputType.Indirect; + return true; + case LightmapRequestOutputType.IrradianceDirect: + integratedOutputType = IntegratedOutputType.Direct; + return true; + case LightmapRequestOutputType.Occupancy: // Occupancy do not need accumulation + return false; + case LightmapRequestOutputType.Validity: + integratedOutputType = IntegratedOutputType.Validity; + return true; + case LightmapRequestOutputType.DirectionalityIndirect: + integratedOutputType = IntegratedOutputType.DirectionalityIndirect; + return true; + case LightmapRequestOutputType.DirectionalityDirect: + integratedOutputType = IntegratedOutputType.DirectionalityDirect; + return true; + case LightmapRequestOutputType.AmbientOcclusion: + integratedOutputType = IntegratedOutputType.AO; + return true; + case LightmapRequestOutputType.Shadowmask: + integratedOutputType = IntegratedOutputType.ShadowMask; + return true; + // These types do not need accumulation: + case LightmapRequestOutputType.Normal: + case LightmapRequestOutputType.ChartIndex: + case LightmapRequestOutputType.OverlapPixelIndex: + case LightmapRequestOutputType.IrradianceEnvironment: + return false; + } + Debug.Assert(false, $"Error unknown LightmapRequestOutputType {type}."); + return false; + } + + // We can ignore the G-Buffer data part of atlassing, we are using stochastic sampling instead. + internal static LightmapDesc[] PopulateLightmapDescsFromAtlassing(in PVRAtlassingData atlassing, in FatInstance[] fatInstances) + { + int atlasCount = atlassing.m_AtlasHashToGBufferHash.Count; + var lightmapDescs = new LightmapDesc[atlasCount]; + foreach (var atlasIdToAtlasHash in atlassing.m_AtlasIdToAtlasHash) + { + int atlasId = atlasIdToAtlasHash.m_AtlasId; + + // Get the array of object ID hashes for the given atlas. + bool found = atlassing.m_AtlasHashToObjectIDHashes.TryGetValue( + atlasIdToAtlasHash.m_AtlasHash, out IndexHash128[] objectIDHashes); + Debug.Assert(found, $"Couldn't find object ID hashes for atlas {atlasIdToAtlasHash.m_AtlasHash}."); + + // Get the GBuffer data for the given atlas, so we can use it to find instance data like IDs and transforms. + found = atlassing.m_AtlasHashToGBufferHash.TryGetValue( + atlasIdToAtlasHash.m_AtlasHash, out _); + if (found == false) + continue; // Skip atlasses without GBuffers, they are used for SSD objects. + + uint lightmapResolution = (uint)atlassing.m_AtlasSizes[atlasId].width; + Debug.Assert(lightmapResolution == atlassing.m_AtlasSizes[atlasId].height, + "The following code assumes that we always have square lightmaps."); + uint antiAliasingSampleCount = (uint)((int)atlasIdToAtlasHash.m_BakeParameters.supersamplingMultiplier * (int)atlasIdToAtlasHash.m_BakeParameters.supersamplingMultiplier); + var lightmapDesc = new LightmapDesc + { + Resolution = lightmapResolution, + PushOff = atlasIdToAtlasHash.m_BakeParameters.pushOff + }; + int instanceCount = objectIDHashes.Length; + var bakeInstances = new BakeInstance[instanceCount]; + int instanceCounter = 0; + foreach (var instanceHash in objectIDHashes) + { + // Get the AtlassedInstanceData for the instance. + found = atlassing.m_InstanceAtlassingData.TryGetValue(instanceHash, + out AtlassedInstanceData atlassedInstanceData); + Debug.Assert(found, $"Didn't find AtlassedInstanceData for instance {instanceHash}."); + Debug.Assert(atlassedInstanceData.m_AtlasId == atlasId, "Atlas ID mismatch."); + + // Get the instance from bakeInput and create a Mesh. + uint bakeInputInstanceIndex = (uint)instanceHash.Index; + ref readonly FatInstance fatInstance = ref fatInstances[bakeInputInstanceIndex]; + LightmapIntegrationHelpers.ComputeOccupiedTexelRegionForInstance( + lightmapResolution, lightmapResolution, atlassedInstanceData.m_LightmapST, fatInstance.UVBoundsSize, fatInstance.UVBoundsOffset, + out Vector4 normalizedOccupiedST, out Vector2Int occupiedTexelSize, out Vector2Int occupiedTexelOffset); + bakeInstances[instanceCounter].Build( + fatInstance.Mesh, + normalizedOccupiedST, + atlassedInstanceData.m_LightmapST, + occupiedTexelSize, + occupiedTexelOffset, + fatInstance.LocalToWorldMatrix, + fatInstance.ReceiveShadows, + fatInstance.LodIdentifier, + bakeInputInstanceIndex); + ++instanceCounter; + } + + lightmapDesc.BakeInstances = bakeInstances; + lightmapDescs[atlasId] = lightmapDesc; + } + + return lightmapDescs; + } + + private static void MakeOutputFolderPathsFullyQualified(ProbeRequest[] probeRequestsToFixUp, string bakeOutputFolderPath) + { + for (int i = 0; i < probeRequestsToFixUp.Length; i++) + { + ref ProbeRequest request = ref probeRequestsToFixUp[i]; + request.outputFolderPath = Path.GetFullPath(request.outputFolderPath, bakeOutputFolderPath); + } + } + + private static void MakeOutputFolderPathsFullyQualified(LightmapRequest[] requestsToFixUp, string bakeOutputFolderPath) + { + for (int i = 0; i < requestsToFixUp.Length; i++) + { + ref LightmapRequest request = ref requestsToFixUp[i]; + request.outputFolderPath = Path.GetFullPath(request.outputFolderPath, bakeOutputFolderPath); + } + } + + internal static bool Bake(string bakeInputPath, string lightmapRequestsPath, string lightProbeRequestsPath, string bakeOutputFolderPath, BakeProgressState progressState) + { + using (new BakeProfilingScope()) + { + // Read BakeInput and requests from LightBaker + if (!BakeInputSerialization.Deserialize(bakeInputPath, out BakeInput bakeInput)) + return false; + if (!BakeInputSerialization.Deserialize(lightmapRequestsPath, out LightmapRequestData lightmapRequestData)) + return false; + if (!BakeInputSerialization.Deserialize(lightProbeRequestsPath, out ProbeRequestData probeRequestData)) + return false; + + // Change output folder paths from relative to absolute + MakeOutputFolderPathsFullyQualified(probeRequestData.requests, bakeOutputFolderPath); + MakeOutputFolderPathsFullyQualified(lightmapRequestData.requests, bakeOutputFolderPath); + IntegrationSettings integrationSettings = GetIntegrationSettings(bakeInput); + + // Setup ray tracing context + RayTracingBackend backend = integrationSettings.Backend; + Debug.Assert(RayTracingContext.IsBackendSupported(backend), $"Backend {backend} is not supported!"); + RayTracingResources rayTracingResources = new RayTracingResources(); + rayTracingResources.Load(); + using var rayTracingContext = new RayTracingContext(backend, rayTracingResources); + + // Setup device context + using UnityComputeDeviceContext deviceContext = new(); + bool initOk = deviceContext.Initialize(); + Assert.IsTrue(initOk, "Failed to initialize DeviceContext."); + + // Create and init world + var worldResources = new WorldResourceSet(); + worldResources.LoadFromAssetDatabase(); + using UnityComputeWorld world = new(); + world.Init(rayTracingContext, worldResources); + + using var samplingResources = new UnityEngine.Rendering.Sampling.SamplingResources(); + samplingResources.Load((uint)UnityEngine.Rendering.Sampling.SamplingResources.ResourceType.All); + + // Deserialize BakeInput, inject data into world + const bool useLegacyBakingBehavior = true; + const bool autoEstimateLUTRange = true; + BakeInputToWorldConversion.InjectBakeInputData(world.PathTracingWorld, autoEstimateLUTRange, in bakeInput, + out Bounds sceneBounds, out world.Meshes, out FatInstance[] fatInstances, out world.LightHandles, + world.TemporaryObjects, UnityComputeWorld.RenderingObjectLayer); + + // Add instances to world + WorldHelpers.AddContributingInstancesToWorld(world.PathTracingWorld, in fatInstances, out var lodInstances, out var lodgroupToContributorInstances); + + // Build world with extracted data + const bool emissiveSampling = true; + world.PathTracingWorld.Build(sceneBounds, deviceContext.GetCommandBuffer(), ref world.ScratchBuffer, samplingResources, emissiveSampling); + + LightmapBakeSettings lightmapBakeSettings = GetLightmapBakeSettings(bakeInput); + // Build array of lightmap descriptors based on the atlassing data and instances. + LightmapDesc[] lightmapDescriptors = PopulateLightmapDescsFromAtlassing(in lightmapRequestData.atlassing, in fatInstances); + SortInstancesByMeshAndResolution(lightmapDescriptors); + + ulong probeWorkSteps = CalculateWorkStepsForProbeRequests(in bakeInput, in probeRequestData); + ulong lightmapWorkSteps = CalculateWorkStepsForLightmapRequests(in lightmapRequestData, lightmapDescriptors, lightmapBakeSettings); + progressState.SetTotalWorkSteps(probeWorkSteps + lightmapWorkSteps); + + if (!ExecuteProbeRequests(in bakeInput, in probeRequestData, deviceContext, useLegacyBakingBehavior, world, bakeInput.lightingSettings.maxBounces, progressState, samplingResources)) + return false; + + if (lightmapRequestData.requests.Length <= 0) + return true; + + // Populate resources structure + LightmapResourceLibrary resources = new(); + resources.Load(world.RayTracingContext); + + if (ExecuteLightmapRequests(in lightmapRequestData, deviceContext, world, in fatInstances, in lodInstances, in lodgroupToContributorInstances, integrationSettings, useLegacyBakingBehavior, resources, progressState, lightmapDescriptors, lightmapBakeSettings, samplingResources) != Result.Success) + return false; + + CoreUtils.Destroy(resources.UVFallbackBufferGenerationMaterial); + + return true; + } + } + + internal static ulong CalculateWorkStepsForProbeRequests(in BakeInput bakeInput, in ProbeRequestData probeRequestData) + { + (uint directSampleCount, uint effectiveIndirectSampleCount) = GetProbeSampleCounts(bakeInput.lightingSettings.probeSampleCounts); + ulong calculatedWorkSteps = 0; + foreach (ProbeRequest probeRequest in probeRequestData.requests) + calculatedWorkSteps += CalculateProbeWorkSteps(probeRequest.count, probeRequest.outputTypeMask, directSampleCount, effectiveIndirectSampleCount, bakeInput.lightingSettings.mixedLightingMode != MixedLightingMode.IndirectOnly, + bakeInput.lightingSettings.maxBounces); + + return calculatedWorkSteps; + } + + private static ulong CalculateProbeWorkSteps(ulong count, ProbeRequestOutputType outputTypeMask, uint directSampleCount, uint effectiveIndirectSampleCount, bool usesProbeOcclusion, uint bounceCount) + { + ulong workSteps = 0; + if (outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceIndirect)) + workSteps += ProbeIntegrator.CalculateWorkSteps((uint)count, effectiveIndirectSampleCount, bounceCount); + if (outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceDirect)) + workSteps += ProbeIntegrator.CalculateWorkSteps((uint)count, directSampleCount, 0); + if (outputTypeMask.HasFlag(ProbeRequestOutputType.Validity)) + workSteps += ProbeIntegrator.CalculateWorkSteps((uint)count, effectiveIndirectSampleCount, 0); + if (outputTypeMask.HasFlag(ProbeRequestOutputType.LightProbeOcclusion) && usesProbeOcclusion) + workSteps += ProbeIntegrator.CalculateWorkSteps((uint)count, effectiveIndirectSampleCount, 0); + + return workSteps; + } + + internal static ulong CalculateWorkStepsForLightmapRequests(in LightmapRequestData lightmapRequestData, LightmapDesc[] lightmapDescriptors, LightmapBakeSettings lightmapBakeSettings) + { + ulong calculatedWorkSteps = 0; + foreach (LightmapRequest r in lightmapRequestData.requests) + { + ref readonly var request = ref r; + if (request.lightmapCount == 0) + continue; + for (int lightmapIndex = 0; lightmapIndex < request.lightmapCount; lightmapIndex++) + { + LightmapDesc currentLightmapDesc = lightmapDescriptors[lightmapIndex]; + Dictionary requestedLightmapTypes = GetRequestedIntegratedOutputTypes(request); + foreach (IntegratedOutputType lightmapType in requestedLightmapTypes.Keys) + { + uint sampleCount = lightmapBakeSettings.GetSampleCount(lightmapType); + foreach (BakeInstance bakeInstance in currentLightmapDesc.BakeInstances) + { + uint instanceWidth = (uint)bakeInstance.TexelSize.x; + uint instanceHeight = (uint)bakeInstance.TexelSize.y; + + calculatedWorkSteps += CalculateIntegratedLightmapWorkSteps(sampleCount, instanceWidth * instanceHeight, lightmapType, lightmapBakeSettings.BounceCount, 1); + } + } + HashSet requestedNonIntegratedLightmapTypes = GetRequestedNonIntegratedOutputTypes(request); + foreach (LightmapRequestOutputType _ in requestedNonIntegratedLightmapTypes) + calculatedWorkSteps += CalculateNonIntegratedLightmapWorkSteps(currentLightmapDesc.Resolution * currentLightmapDesc.Resolution); + } + } + + return calculatedWorkSteps; + } + + private static ulong CalculateIntegratedLightmapWorkSteps(uint samplesPerTexel, uint chunkSize, IntegratedOutputType outputType, uint bounces, uint multiplier) + { + uint bouncesMultiplier = outputType == IntegratedOutputType.Indirect + ? 0 == bounces ? 1 : bounces + : 1; + + return samplesPerTexel*chunkSize*bouncesMultiplier*multiplier; + } + + private static ulong CalculateNonIntegratedLightmapWorkSteps(uint lightmapResolution) => lightmapResolution; + + private static IntegrationSettings GetIntegrationSettings(in BakeInput bakeInput) + { + IntegrationSettings retVal = IntegrationSettings.Default; + retVal.Backend = RayTracingBackend.Compute; + // TODO(pema.malling) + // retVal.Backend = + // bakeInput.lightingSettings.useHardwareRayTracing && RayTracingContext.IsBackendSupported(RayTracingBackend.Hardware) ? + // RayTracingBackend.Hardware : RayTracingBackend.Compute; + + return retVal; + } + + internal static LightmapBakeSettings GetLightmapBakeSettings(in BakeInput bakeInput) + { + // Lightmap settings + LightmapBakeSettings lightmapBakeSettings = new() + { + AOSampleCount = math.max(0, bakeInput.lightingSettings.lightmapSampleCounts.indirectSampleCount), + DirectSampleCount = math.max(0, bakeInput.lightingSettings.lightmapSampleCounts.directSampleCount), + IndirectSampleCount = math.max(0, bakeInput.lightingSettings.lightmapSampleCounts.indirectSampleCount), + BounceCount = math.max(0, bakeInput.lightingSettings.maxBounces), + AOMaxDistance = math.max(0.0f, bakeInput.lightingSettings.aoDistance) + }; + lightmapBakeSettings.ValiditySampleCount = lightmapBakeSettings.IndirectSampleCount; + return lightmapBakeSettings; + } + + [Flags] + private enum RequestedSubOutput + { + PrimaryTexture = 1 << 0, + DirectionalityTexture = 1 << 1 + } + + private struct HashedInstanceIndex : IComparable + { + public BakeInstance bakeInstance; + public int texelCount; + public int offsetX; + public int offsetY; + public int hashCode; + + public int CompareTo(HashedInstanceIndex other) + { + int size = other.texelCount - texelCount; + if (size != 0) + return size; + int xOffset = other.offsetX - offsetX; + if (xOffset != 0) + return xOffset; + int yOffset = offsetY - other.offsetY; + if (yOffset != 0) + return yOffset; + return hashCode - other.hashCode; + } + }; + + internal static void SortInstancesByMeshAndResolution(LightmapDesc[] lightmapDescriptors) + { + int lightmapDescIndex = 0; + foreach (var lightmapDesc in lightmapDescriptors) + { + HashedInstanceIndex[] hashedInstances = new HashedInstanceIndex[lightmapDesc.BakeInstances.Length]; + int instanceIndex = 0; + foreach (var instance in lightmapDesc.BakeInstances) + { + int hashCode = System.HashCode.Combine(instance.TexelSize.x, instance.TexelSize.y); + hashedInstances[instanceIndex] = new(); + hashedInstances[instanceIndex].bakeInstance = instance; + hashedInstances[instanceIndex].texelCount = instance.TexelSize.x * instance.TexelSize.y; + hashedInstances[instanceIndex].offsetX = instance.TexelOffset.x; + hashedInstances[instanceIndex].offsetY = instance.TexelOffset.y; + hashedInstances[instanceIndex].hashCode = hashCode; + instanceIndex++; + } + Array.Sort(hashedInstances); + instanceIndex = 0; + foreach (var hashedInstance in hashedInstances) + { + lightmapDescriptors[lightmapDescIndex].BakeInstances[instanceIndex++] = hashedInstance.bakeInstance; + } + lightmapDescIndex++; + } + } + + // Gets all meshes used by the specified instances, ordered by the first instance they are used in. + private static List GetMeshesInInstanceOrder(LightmapDesc[] lightmapDescriptors) + { + List sortedMeshes = new List(); + HashSet seenMeshes = new HashSet(); + foreach (var lightmapDesc in lightmapDescriptors) + { + foreach (var instance in lightmapDesc.BakeInstances) + { + if (seenMeshes.Add(instance.Mesh)) + { + sortedMeshes.Add(instance.Mesh); + } + } + } + return sortedMeshes; + } + + private static bool AnyLightmapRequestHasOutput(LightmapRequest[] requests, LightmapRequestOutputType type) + { + foreach (var req in requests) + { + if (req.outputTypeMask.HasFlag(type)) + { + return true; + } + } + + return false; + } + + private static bool EnsureInstanceInCacheAndClearExistingEntries(CommandBuffer cmd, + LightmappingContext lightmappingContext, + BakeInstance bakeInstance) + { + var bakeInstances = new[] { bakeInstance }; + if (!lightmappingContext.ResourceCache.CacheIsHot(bakeInstances)) + { + // Build the required resources for the current instance + GraphicsHelpers.Flush(cmd); // need to flush as we are removing resources from the cache which might be in use + lightmappingContext.ResourceCache.FreeResources(bakeInstances); + + if (!lightmappingContext.ResourceCache.AddResources( + bakeInstances, lightmappingContext.World.RayTracingContext, cmd, + lightmappingContext.IntegratorContext.UVFallbackBufferBuilder)) + { + return false; + } + // Flush as there can be a substantial amount of work done in the commandbuffer + GraphicsHelpers.Flush(cmd); + } + + return true; + } + + private static bool GetInstanceUVResources( + CommandBuffer cmd, + LightmappingContext lightmappingContext, + BakeInstance bakeInstance, + out UVMesh uvMesh, + out UVAccelerationStructure uvAS, + out UVFallbackBuffer uvFallbackBuffer) + { + uvMesh = default; + uvAS = default; + uvFallbackBuffer = default; + + if (!EnsureInstanceInCacheAndClearExistingEntries(cmd, lightmappingContext, bakeInstance)) + return false; + + bool gotResources = lightmappingContext.ResourceCache.GetResources(new[] { bakeInstance }, + out UVMesh[] uvMeshes, out UVAccelerationStructure[] uvAccelerationStructures, out UVFallbackBuffer[] uvFallbackBuffers); + if (!gotResources) + return false; + + uvMesh = uvMeshes[0]; + uvAS = uvAccelerationStructures[0]; + uvFallbackBuffer = uvFallbackBuffers[0]; + + return true; + } + + static void GetLodZeroInstanceMasks(bool pathtracingShader, uint[] originalMasks, Span lodZeroMasks) + { + for (int j = 0; j < originalMasks.Length; j++) + { + // if we don't need bounce rays, we just make the lod0 invisible (instanceMask=0), so that only the current lod can be traced against + // otherwise we set bits so that we can select one or the other using the raymask in the shader + if (pathtracingShader) + { + lodZeroMasks[j] = (uint)InstanceFlags.LOD_ZERO_FOR_LIGHTMAP_INSTANCE; + if ((originalMasks[j] & (uint)InstanceFlags.SHADOW_RAY_VIS_MASK) != 0) + lodZeroMasks[j] |= (uint)InstanceFlags.LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW; + } + else + { + lodZeroMasks[j] = 0; + } + } + } + + static void GetCurrentLodInstanceMasks(bool pathtracingShader, uint[] originalMasks, Span currentLodMasks) + { + for (int j = 0; j < originalMasks.Length; j++) + { + if (pathtracingShader) + { + currentLodMasks[j] = (uint)InstanceFlags.CURRENT_LOD_FOR_LIGHTMAP_INSTANCE; + if ((originalMasks[j] & (uint)InstanceFlags.SHADOW_RAY_VIS_MASK) != 0) + currentLodMasks[j] |= (uint)InstanceFlags.CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW; + } + else + { + currentLodMasks[j] = originalMasks[j]; + } + } + } + + internal static InstanceHandle[] AddLODInstances(World world, CommandBuffer cmd, LodIdentifier lodIdentifier, in List lodInstancesBuildData, bool pathtracingShader, Dictionary> lodgroupToContributorInstances) + { + Debug.Assert(lodIdentifier.IsValid() && !lodIdentifier.IsContributor()); + + // add current lod instances + var instanceHandles = new InstanceHandle[lodInstancesBuildData.Count]; + int i = 0; + uint currentLodLevel = lodIdentifier.MinLodLevelMask(); + foreach (LodInstanceBuildData lodBuildData in lodInstancesBuildData) + { + if ((lodBuildData.LodMask & currentLodLevel) == 0) + continue; + + Span currentLodMasks = stackalloc uint[lodBuildData.Masks.Length]; + GetCurrentLodInstanceMasks(pathtracingShader, lodBuildData.Masks, currentLodMasks); + + instanceHandles[i++] = world.AddInstance(lodBuildData.Mesh, lodBuildData.Materials, currentLodMasks, UnityComputeWorld.RenderingObjectLayer, lodBuildData.LocalToWorldMatrix, lodBuildData.Bounds, lodBuildData.IsStatic, lodBuildData.Filter, true); + } + Array.Resize(ref instanceHandles, i); + + // update lod0 instance mask + List lodZeroInstances; + if (lodgroupToContributorInstances.TryGetValue(lodIdentifier.LodGroup, out lodZeroInstances)) + { + foreach (var lodZeroInstance in lodZeroInstances) + { + if ((lodZeroInstance.LodMask & currentLodLevel) != 0) + continue; + + Span lodZeroMasks = stackalloc uint[lodZeroInstance.Masks.Length]; + GetLodZeroInstanceMasks(pathtracingShader, lodZeroInstance.Masks, lodZeroMasks); + + world.UpdateInstanceMask(lodZeroInstance.InstanceHandle, lodZeroMasks); + } + } + + return instanceHandles; + } + + internal static void RemoveLODInstances(World world, CommandBuffer cmd, LodIdentifier lodIdentifier, Span currentLodInstanceHandles, Dictionary> lodgroupToContributorInstances) + { + if (!lodIdentifier.IsValid() || lodIdentifier.IsContributor()) + return; + + // Remove current lod instances + foreach (var instanceHandle in currentLodInstanceHandles) + world.RemoveInstance(instanceHandle); + + // Restore lod0 instances + List lodZeroInstances; + if (lodgroupToContributorInstances.TryGetValue(lodIdentifier.LodGroup, out lodZeroInstances)) + { + foreach (var lodZeroInstance in lodZeroInstances) + world.UpdateInstanceMask(lodZeroInstance.InstanceHandle, lodZeroInstance.Masks); + } + } + + private static InstanceHandle[] PrepareLodInstances( + CommandBuffer cmd, + UnityComputeWorld world, + BakeInstance bakeInstance, + Dictionary> lodInstances, + Dictionary> lodgroupToContributorInstances, + bool isPathTracingPass) + { + if (bakeInstance.LodIdentifier.IsValid() && !bakeInstance.LodIdentifier.IsContributor()) + { + // AddLODInstances calls _pathTracingWorld.Add/RemoveInstance(...) that doesn't take a cmd buffer arg. Need to flush as cmdbuffer and immediate calls cannot be mixed. + GraphicsHelpers.Flush(cmd); + + var currentLodInstancesBuildData = lodInstances[bakeInstance.LodIdentifier.LodGroup]; + InstanceHandle[] handles = AddLODInstances(world.PathTracingWorld, cmd, bakeInstance.LodIdentifier, currentLodInstancesBuildData, isPathTracingPass, lodgroupToContributorInstances); + world.BuildAccelerationStructure(cmd); + return handles; + } + return null; + } + + private static void ClearLodInstances( + CommandBuffer cmd, + UnityComputeWorld world, + BakeInstance bakeInstance, + InstanceHandle[] currentLodInstances, + Dictionary> lodgroupToContributorInstances) + { + if (bakeInstance.LodIdentifier.IsValid() && !bakeInstance.LodIdentifier.IsContributor()) + { + // RemoveLODInstances calls _pathTracingWorld.Add/RemoveInstance(...) that doesn't take a cmd buffer arg. Need to flush as cmdbuffer and immediate calls cannot be mixed. + GraphicsHelpers.Flush(cmd); + + RemoveLODInstances(world.PathTracingWorld, cmd, bakeInstance.LodIdentifier, currentLodInstances, lodgroupToContributorInstances); + world.BuildAccelerationStructure(cmd); + } + } + + private static Result IntegrateLightmapInstance(CommandBuffer cmd, + int lightmapIndex, + IntegratedOutputType integratedOutputType, + BakeInstance bakeInstance, + in Dictionary> lodInstances, + in Dictionary> lodgroupToContributorInstances, + LightmappingContext lightmappingContext, + UVAccelerationStructure uvAS, + UVFallbackBuffer uvFallbackBuffer, + IntegrationSettings integrationSettings, + LightmapBakeSettings lightmapBakeSettings, + bool doDirectionality, + BakeProgressState progressState, + LightmapIntegrationHelpers.GPUSync gpuSync, + bool debugDispatches) + { + Debug.Assert(!lightmappingContext.ExpandedBufferNeedsUpdating(lightmapBakeSettings.ExpandedBufferSize), "The integration data must be allocated at this point."); + + // Bake the lightmap instances + int bakeDispatches = 0; + LightmapBakeState bakeState = new(); + bakeState.Init(); + uint samples = 0; + bool isInstanceDone = false; + uint dispatchCount = 0; + uint instanceWidth = (uint)bakeInstance.TexelSize.x; + uint instanceHeight = (uint)bakeInstance.TexelSize.y; + InstanceHandle[] currentLodInstances = null; + + // accumulate the instance + System.Diagnostics.Stopwatch instanceFlushStopwatch = System.Diagnostics.Stopwatch.StartNew(); + System.Diagnostics.Stopwatch dispatchStopwatch = System.Diagnostics.Stopwatch.StartNew(); + do + { + if (debugDispatches) + { + gpuSync.Sync(cmd); + dispatchStopwatch.Restart(); + Console.WriteLine($"Begin pass {bakeDispatches} for lm: {lightmapIndex}, type: {integratedOutputType}, sample count: {bakeState.SampleIndex}, res: [{bakeInstance.TexelSize.x} x {bakeInstance.TexelSize.y}], offset: [{bakeInstance.TexelOffset.x} x {bakeInstance.TexelOffset.y}]."); + } + + bool isInstanceStart = (bakeState.SampleIndex == 0); + if (isInstanceStart) + { + bool isPathTracingPass = integratedOutputType == IntegratedOutputType.Indirect || integratedOutputType == IntegratedOutputType.DirectionalityIndirect; + currentLodInstances = PrepareLodInstances(cmd, lightmappingContext.World, bakeInstance, lodInstances, lodgroupToContributorInstances, isPathTracingPass); + } + + // Enqueue some baking work in the command buffer. + cmd.BeginSample($"Bake {integratedOutputType}"); + dispatchCount++; + + uint passSamplesPerTexel = BakeLightmapDriver.AccumulateLightmapInstance( + bakeState, + bakeInstance, + lightmapBakeSettings, + integratedOutputType, + lightmappingContext, + uvAS, + uvFallbackBuffer, + doDirectionality, + out uint chunkSize, + out isInstanceDone); + + samples += instanceWidth * instanceHeight * passSamplesPerTexel; + cmd.EndSample($"Bake {integratedOutputType}"); + + if (isInstanceDone) + { + ClearLodInstances(cmd, lightmappingContext.World, bakeInstance, currentLodInstances, lodgroupToContributorInstances); + } + + if (debugDispatches) + { + gpuSync.Sync(cmd); + dispatchStopwatch.Stop(); + Console.WriteLine($"Finished pass {bakeDispatches}. Elapsed ms: {dispatchStopwatch.ElapsedMilliseconds}"); + } + + bakeDispatches++; + + // Execute the baking work scheduled in BakeLightmaps. + if (bakeDispatches % integrationSettings.MaxDispatchesPerFlush != 0 && !isInstanceDone) + continue; + + // Chip-off work steps based on the chunk done so far + ulong completedWorkSteps = + CalculateIntegratedLightmapWorkSteps(passSamplesPerTexel, chunkSize, integratedOutputType, lightmapBakeSettings.BounceCount, integrationSettings.MaxDispatchesPerFlush); + gpuSync.RequestAsyncReadback(cmd, _ => progressState.IncrementCompletedWorkSteps(completedWorkSteps)); + GraphicsHelpers.Flush(cmd); + + if (!debugDispatches) + continue; + + gpuSync.Sync(cmd); + instanceFlushStopwatch.Stop(); + Console.WriteLine($"Bake dispatch flush -> Instance: {bakeInstance.Mesh.GetEntityId()}, dispatches {dispatchCount}, Samples: \t{samples}\t. Elapsed ms:\t{instanceFlushStopwatch.ElapsedMilliseconds}"); + samples = 0; + dispatchCount = 0; + instanceFlushStopwatch.Restart(); + + //LightmapIntegrationHelpers.WriteRenderTexture(cmd, $"Temp/lm{lightmapIndex}_type{lightmapType}_pass{bakeDispatches}.r2d", lightmappingContext.AccumulatedOutput, lightmappingContext.AccumulatedOutput.width, lightmappingContext.AccumulatedOutput.height); + } + while (isInstanceDone == false); + + return Result.Success; + } + + internal static Result ExecuteLightmapRequests( + in LightmapRequestData lightmapRequestData, + UnityComputeDeviceContext deviceContext, + UnityComputeWorld world, + in FatInstance[] fatInstances, + in Dictionary> lodInstances, + in Dictionary> lodgroupToContributorInstances, + IntegrationSettings integrationSettings, + bool useLegacyBakingBehavior, + LightmapResourceLibrary lightmapResourceLib, + BakeProgressState progressState, + LightmapDesc[] lightmapDescriptors, + LightmapBakeSettings lightmapBakeSettings, + UnityEngine.Rendering.Sampling.SamplingResources samplingResources) + { + using var lightmappingContext = new LightmappingContext(); + + bool debugDispatches = integrationSettings.DebugDispatches; + bool doDirectionality = AnyLightmapRequestHasOutput(lightmapRequestData.requests, LightmapRequestOutputType.DirectionalityDirect) || AnyLightmapRequestHasOutput(lightmapRequestData.requests, LightmapRequestOutputType.DirectionalityIndirect); + + // Find the max index count in any mesh, so we can pre-allocate various buffers based on it. + uint maxIndexCount = 1; + for (int meshIdx = 0; meshIdx < world.Meshes.Length; ++meshIdx) + { + maxIndexCount = Math.Max(maxIndexCount, world.Meshes[meshIdx].GetTotalIndexCount()); + } + + (int width, int height)[] atlasSizes = lightmapRequestData.atlassing.m_AtlasSizes; + int initialLightmapResolution = atlasSizes.Length > 0 ? atlasSizes[0].width : 1024; + if (!lightmappingContext.Initialize(deviceContext, initialLightmapResolution, initialLightmapResolution, world, maxIndexCount, lightmapResourceLib)) + return Result.InitializeFailure; + + lightmappingContext.IntegratorContext.Initialize(samplingResources, lightmapResourceLib, !useLegacyBakingBehavior); + + // Chart identification happens in multithreaded fashion on the CPU. We start it immediately so it can run in tandem with other work. + bool usesChartIdentification = AnyLightmapRequestHasOutput(lightmapRequestData.requests, LightmapRequestOutputType.ChartIndex) || + AnyLightmapRequestHasOutput(lightmapRequestData.requests, LightmapRequestOutputType.OverlapPixelIndex); + using ParallelChartIdentification chartIdentification = usesChartIdentification ? new ParallelChartIdentification(GetMeshesInInstanceOrder(lightmapDescriptors)) : null; + if (usesChartIdentification) + chartIdentification.Start(); + + if (debugDispatches) + { + for (int i = 0; i < lightmapDescriptors.Length; ++i) + { + Console.WriteLine($"Desc:"); + int instanceIndex = 0; + foreach (var bakeInstance in lightmapDescriptors[i].BakeInstances) + Console.WriteLine($" Instance[{instanceIndex++}]: {bakeInstance.Mesh.GetEntityId()}, res: [{bakeInstance.TexelSize.x} x {bakeInstance.TexelSize.y}], offset: [{bakeInstance.TexelOffset.x} x {bakeInstance.TexelOffset.y}]."); + } + } + + CommandBuffer cmd = lightmappingContext.GetCommandBuffer(); + using LightmapIntegrationHelpers.GPUSync gpuSync = new(); // used for sync points in debug mode + gpuSync.Create(); + + // process requests + for (int requestIndex = 0; requestIndex < lightmapRequestData.requests.Length; requestIndex++) + { + if (progressState.WasCancelled()) + return Result.Cancelled; + + ref readonly var request = ref lightmapRequestData.requests[requestIndex]; + if (request.lightmapCount == 0) + continue; + + var createDirResult = Directory.CreateDirectory(request.outputFolderPath); + if (createDirResult.Exists == false) + return Result.CreateDirectoryFailure; + + Dictionary integratedRequestOutputs = GetRequestedIntegratedOutputTypes(request); + bool needsNormalsForDirectionality = request.outputTypeMask.HasFlag(LightmapRequestOutputType.DirectionalityIndirect) || request.outputTypeMask.HasFlag(LightmapRequestOutputType.DirectionalityDirect); + bool writeNormals = request.outputTypeMask.HasFlag(LightmapRequestOutputType.Normal); + + // Request related bake settings + for (int lightmapIndex = 0; lightmapIndex < request.lightmapCount; lightmapIndex++) + { + if (progressState.WasCancelled()) + return Result.Cancelled; + + LightmapDesc currentLightmapDesc = lightmapDescriptors[lightmapIndex]; + lightmapBakeSettings.PushOff = currentLightmapDesc.PushOff; + int resolution = (int)currentLightmapDesc.Resolution; + UInt64 lightmapSize = (UInt64)resolution * (UInt64)resolution; + lightmapBakeSettings.ExpandedBufferSize = LightmapRequest.TilingModeToLightmapExpandedBufferSize(request.tilingMode); + + // allocate expanded buffer + if (lightmappingContext.ExpandedBufferNeedsUpdating(lightmapBakeSettings.ExpandedBufferSize)) + { + GraphicsHelpers.Flush(cmd); // need to flush as we are removing resources from the cache which might be in use + if (!lightmappingContext.InitializeExpandedBuffer(lightmapBakeSettings.ExpandedBufferSize)) + return Result.InitializeExpandedBufferFailure; + // The scratch buffer is used for tracing rays in the lightmap integrators it needs to be sufficiently large to ray trace an expanded buffer + uint scratchBufferSize = (uint)lightmapBakeSettings.ExpandedBufferSize; + lightmappingContext.InitializeTraceScratchBuffer(scratchBufferSize, 1, 1); + if (debugDispatches) + Console.WriteLine($"Built expanded buffer for {lightmapBakeSettings.ExpandedBufferSize} samples, lm w: {lightmappingContext.AccumulatedOutput.width}, lm h: {lightmappingContext.AccumulatedOutput.height}]."); + } + + ref RenderTexture accumulatedOutput = ref lightmappingContext.AccumulatedOutput; + if ((accumulatedOutput.width != resolution) || (accumulatedOutput.height != resolution)) + if (!lightmappingContext.SetOutputResolution(resolution, resolution)) + return Result.InitializeFailure; + + // Bake normals output + RenderTexture normalBuffer = null; + if (needsNormalsForDirectionality || writeNormals) + { + lightmappingContext.ClearOutputs(); + + IRayTracingShader normalShader = lightmapResourceLib.NormalAccumulationShader; + GraphicsBuffer compactedGBufferLength = lightmappingContext.CompactedGBufferLength; + GraphicsBuffer indirectDispatchBuffer = lightmappingContext.IndirectDispatchBuffer; + GraphicsBuffer indirectRayTracingDispatchBuffer = lightmappingContext.IndirectDispatchRayTracingBuffer; + + uint maxChunkSize = (uint)lightmappingContext.ExpandedOutput.count; + var expansionShaders = lightmapResourceLib.ExpansionHelpers; + var compactionKernel = expansionShaders.FindKernel("CompactGBuffer"); + var populateCopyDispatchKernel = expansionShaders.FindKernel("PopulateCopyDispatch"); + var copyToLightmapKernel = expansionShaders.FindKernel("AdditivelyCopyCompactedTo2D"); + var populateNormalShaderDispatchKernel = expansionShaders.FindKernel("PopulateAccumulationDispatch"); + + expansionShaders.GetKernelThreadGroupSizes(copyToLightmapKernel, out uint copyThreadGroupSizeX, out uint copyThreadGroupSizeY, out uint copyThreadGroupSizeZ); + Debug.Assert(copyThreadGroupSizeY == 1 && copyThreadGroupSizeZ == 1); + + foreach (var bakeInstance in currentLightmapDesc.BakeInstances) + { + if (progressState.WasCancelled()) + return Result.Cancelled; + + if (!GetInstanceUVResources(cmd, lightmappingContext, bakeInstance, out _, out var uvAS, out var uvFallbackBuffer)) + return Result.AddResourcesToCacheFailure; + + InstanceHandle[] currentLodInstances = PrepareLodInstances(cmd, lightmappingContext.World, bakeInstance, lodInstances, lodgroupToContributorInstances, false); + var instanceGeometryIndex = lightmappingContext.World.PathTracingWorld.GetAccelerationStructure().GeometryPool.GetInstanceGeometryIndex(bakeInstance.Mesh); + + uint instanceWidth = (uint)bakeInstance.TexelSize.x; + uint instanceHeight = (uint)bakeInstance.TexelSize.y; + UInt64 instanceSize = (UInt64)instanceWidth * (UInt64)instanceHeight; + UInt64 chunkTexelOffset = 0; + do + { + uint2 chunkOffset = new uint2((uint)(chunkTexelOffset % (UInt64)instanceWidth), (uint)(chunkTexelOffset / (UInt64)instanceWidth)); + uint remainingTexels = instanceWidth - chunkOffset.x + ((instanceHeight - 1) - chunkOffset.y) * instanceWidth; + uint chunkSize = math.min(maxChunkSize, remainingTexels); + Debug.Assert(remainingTexels > 0); + + cmd.BeginSample($"Bake Normals"); + ExpansionHelpers.CompactGBuffer( + cmd, + expansionShaders, + compactionKernel, + instanceWidth, + chunkSize, + chunkOffset, + uvFallbackBuffer, + compactedGBufferLength, + lightmappingContext.CompactedTexelIndices); + + // build a gBuffer for the chunk + ExpansionHelpers.GenerateGBuffer( + cmd, + lightmappingContext.IntegratorContext.GBufferShader, + lightmappingContext.GBuffer, + lightmappingContext.TraceScratchBuffer, + lightmappingContext.IntegratorContext.SamplingResources, + uvAS, + uvFallbackBuffer, + compactedGBufferLength, + lightmappingContext.CompactedTexelIndices, + bakeInstance.TexelOffset, + chunkOffset, + chunkSize, + expandedSampleWidth: 1, + passSampleCount: 1, + sampleOffset: 0, + AntiAliasingType.SuperSampling, + superSampleWidth: 1); + chunkTexelOffset += chunkSize; + + // now perform the normal generation for the chunk + // geometry pool bindings + Util.BindAccelerationStructure(cmd, normalShader, world.PathTracingWorld.GetAccelerationStructure()); + + var requiredSizeInBytes = normalShader.GetTraceScratchBufferRequiredSizeInBytes((uint)chunkSize, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(lightmappingContext.TraceScratchBuffer.count * lightmappingContext.TraceScratchBuffer.stride); + Debug.Assert(lightmappingContext.TraceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + normalShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, bakeInstance.LocalToWorldMatrix); + normalShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, bakeInstance.LocalToWorldMatrixNormals); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, (int)instanceWidth); + + normalShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, lightmappingContext.GBuffer); + normalShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, lightmappingContext.CompactedTexelIndices); + normalShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, lightmappingContext.ExpandedOutput); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, 1); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, normalShader, expansionShaders, populateNormalShaderDispatchKernel, 1, compactedGBufferLength, indirectRayTracingDispatchBuffer); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, 0); + normalShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, 1); + cmd.BeginSample("Normal Generation"); + normalShader.Dispatch(cmd, lightmappingContext.TraceScratchBuffer, indirectRayTracingDispatchBuffer); + cmd.EndSample("Normal Generation"); + + // copy back to the output + ExpansionHelpers.PopulateCopyToLightmapIndirectDispatch(cmd, expansionShaders, populateCopyDispatchKernel, copyThreadGroupSizeX, compactedGBufferLength, indirectDispatchBuffer); + ExpansionHelpers.CopyToLightmap(cmd, expansionShaders, copyToLightmapKernel, 1, (int)instanceWidth, bakeInstance.TexelOffset, chunkOffset, compactedGBufferLength, lightmappingContext.CompactedTexelIndices, lightmappingContext.ExpandedOutput, indirectDispatchBuffer, lightmappingContext.AccumulatedOutput); + cmd.EndSample("Bake Normals"); + } + while (chunkTexelOffset < instanceSize); + + ClearLodInstances(cmd, lightmappingContext.World, bakeInstance, currentLodInstances, lodgroupToContributorInstances); + } + + if (writeNormals) + { + if (LightmapIntegrationHelpers.WriteLightmap(cmd, accumulatedOutput, "normal", lightmapIndex, request.outputFolderPath) == false) + return Result.WriteToDiskFailure; + } + + if (needsNormalsForDirectionality) + { + // Hang on to normal buffer for normalization of directionality + normalBuffer = LightmappingContext.MakeRenderTexture(accumulatedOutput.width, accumulatedOutput.height, "TempNormalBuffer"); + cmd.CopyTexture(accumulatedOutput, normalBuffer); + } + + ulong workSteps = CalculateNonIntegratedLightmapWorkSteps((uint) lightmapSize); + progressState.IncrementCompletedWorkSteps(workSteps); + } + + // Bake occupancy output + if (request.outputTypeMask.HasFlag(LightmapRequestOutputType.Occupancy)) + { + lightmappingContext.ClearOutputs(); + + foreach (var bakeInstance in currentLightmapDesc.BakeInstances) + { + if (!GetInstanceUVResources(cmd, lightmappingContext, bakeInstance, out _, out _, out var uvFallbackBuffer)) + return Result.AddResourcesToCacheFailure; + + lightmappingContext.IntegratorContext.LightmapOccupancyIntegrator.Accumulate( + cmd, + bakeInstance.TexelSize, + bakeInstance.TexelOffset, + uvFallbackBuffer, + accumulatedOutput); + } + + if (LightmapIntegrationHelpers.WriteLightmap(cmd, accumulatedOutput, "occupancy", lightmapIndex, request.outputFolderPath) == false) + return Result.WriteToDiskFailure; + + ulong workSteps = CalculateNonIntegratedLightmapWorkSteps((uint) lightmapSize); + progressState.IncrementCompletedWorkSteps(workSteps); + } + + // Bake chart index output + if (request.outputTypeMask.HasFlag(LightmapRequestOutputType.ChartIndex)) + { + var rasterizer = lightmappingContext.ChartRasterizer; + + cmd.SetRenderTarget(lightmappingContext.AccumulatedOutput); + cmd.ClearRenderTarget(false, true, new Color(-1, -1, -1, -1)); // -1 = No chart + + // Keep track of how many charts we have rasterized, so we can ensure uniqueness across the entire lightmap. + uint chartIndexOffset = 0; + + foreach (var bakeInstance in currentLightmapDesc.BakeInstances) + { + if (!GetInstanceUVResources(cmd, lightmappingContext, bakeInstance, out var uvMesh, out _, out _)) + return Result.AddResourcesToCacheFailure; + + var vertexToChartId = chartIdentification.CompleteAndGetResult(bakeInstance.Mesh); + cmd.SetBufferData(lightmappingContext.ChartRasterizerBuffers.vertexToChartID, vertexToChartId.VertexChartIndices); + + ChartRasterizer.PrepareRasterizeSoftware(cmd, uvMesh.Mesh, + lightmappingContext.ChartRasterizerBuffers.vertex, lightmappingContext.ChartRasterizerBuffers.vertexToOriginalVertex); + rasterizer.RasterizeSoftware(cmd, lightmappingContext.ChartRasterizerBuffers.vertex, + lightmappingContext.ChartRasterizerBuffers.vertexToOriginalVertex, lightmappingContext.ChartRasterizerBuffers.vertexToChartID, + uvMesh.Mesh.GetTotalIndexCount(), bakeInstance.NormalizedOccupiedST, chartIndexOffset, lightmappingContext.AccumulatedOutput); + + chartIndexOffset += vertexToChartId.ChartCount; + } + + // Readback texture + Vector4[] pixels = null; + cmd.RequestAsyncReadback(lightmappingContext.AccumulatedOutput, 0, request => pixels = request.GetData().ToArray()); + cmd.WaitAllAsyncReadbackRequests(); + + GraphicsHelpers.Flush(cmd); + + // Write it to disk + int[] chartIndices = new int[pixels.Length]; + for (int i = 0; i < pixels.Length; i++) + chartIndices[i] = (int)pixels[i].x; + if (!SaveArrayToFile(request.outputFolderPath + $"/chartIndex{lightmapIndex}.int", chartIndices.Length, chartIndices)) + return Result.WriteToDiskFailure; + + ulong workSteps = CalculateNonIntegratedLightmapWorkSteps((uint)lightmapSize); + progressState.IncrementCompletedWorkSteps(workSteps); + } + + // Compute pixel overlap indices + if (request.outputTypeMask.HasFlag(LightmapRequestOutputType.OverlapPixelIndex)) + { + using var overlapDetection = new UVOverlapDetection(); + overlapDetection.Initialize(UVOverlapDetection.LoadShader(), (uint)resolution, maxIndexCount, (uint)fatInstances.Length); + + // Mark overlaps in every instance. Keep track of how many charts we have processed, + // so we can ensure uniqueness across the entire lightmap. + uint chartIndexOffset = 0; + foreach (var bakeInstance in currentLightmapDesc.BakeInstances) + { + if (!GetInstanceUVResources(cmd, lightmappingContext, bakeInstance, out var uvMesh, out _, out _)) + return Result.AddResourcesToCacheFailure; + + var vertexToChartId = chartIdentification.CompleteAndGetResult(bakeInstance.Mesh); + + cmd.BeginSample("UV Overlap Detection"); + overlapDetection.MarkOverlapsInInstance( + cmd, + uvMesh.Mesh, + vertexToChartId.VertexChartIndicesIgnoringNormals, + bakeInstance.NormalizedOccupiedST, + bakeInstance.InstanceIndex, + chartIndexOffset); + cmd.EndSample("UV Overlap Detection"); + + chartIndexOffset += vertexToChartId.ChartCount; + } + + // Readback result, write to disk + overlapDetection.CompactAndReadbackOverlaps( + cmd, + out var uniqueOverlapPixelIndices, + out var uniqueOverlapInstanceIndices); + + SaveArrayToFile(request.outputFolderPath + $"/overlapPixelIndex{lightmapIndex}.uint", uniqueOverlapPixelIndices.Length, uniqueOverlapPixelIndices); + SaveArrayToFile(request.outputFolderPath + $"/uvOverlapInstanceIds{lightmapIndex}.size_t", uniqueOverlapInstanceIndices.Length, uniqueOverlapInstanceIndices); + + ulong workSteps = CalculateNonIntegratedLightmapWorkSteps((uint)lightmapSize); + progressState.IncrementCompletedWorkSteps(workSteps); + } + + // Bake all the integrator components requests + foreach (var integratedRequestOutputType in integratedRequestOutputs) + { + if (progressState.WasCancelled()) + return Result.Cancelled; + + lightmappingContext.ClearOutputs(); + + IntegratedOutputType integratedOutputType = integratedRequestOutputType.Key; + foreach (var bakeInstance in currentLightmapDesc.BakeInstances) + { + if (!GetInstanceUVResources(cmd, lightmappingContext, bakeInstance, out _, out var uvAS, out var uvFallbackBuffer)) + return Result.AddResourcesToCacheFailure; + + var result = IntegrateLightmapInstance( + cmd, + lightmapIndex, + integratedRequestOutputType.Key, + bakeInstance, + lodInstances, + lodgroupToContributorInstances, + lightmappingContext, + uvAS, + uvFallbackBuffer, + integrationSettings, + lightmapBakeSettings, + doDirectionality, + progressState, + gpuSync, + debugDispatches + ); + + if (result != Result.Success) + return result; + } + + // Copy out the results + bool hasDirectionality = integratedRequestOutputType.Value.HasFlag(RequestedSubOutput.DirectionalityTexture); + if (hasDirectionality) + { + Debug.Assert(normalBuffer != null, "Normal buffer must be set when directionality is requested."); + Debug.Assert(needsNormalsForDirectionality); + + // This uses the accumulatedOutput to get the sample count, so directionality must be normalized first since the sample count will be normalized when the lightmap is normalized + // Currently the directional output from LightBaker is not normalized - so for now we don't do it here either. JIRA: https://jira.unity3d.com/browse/LIGHT-1814 + switch (integratedOutputType) + { + case IntegratedOutputType.Direct: + lightmappingContext.IntegratorContext.LightmapDirectIntegrator.NormalizeDirectional(cmd, lightmappingContext.AccumulatedDirectionalOutput, accumulatedOutput, normalBuffer); + break; + case IntegratedOutputType.Indirect: + lightmappingContext.IntegratorContext.LightmapIndirectIntegrator.NormalizeDirectional(cmd, lightmappingContext.AccumulatedDirectionalOutput, accumulatedOutput, normalBuffer); + break; + } + } + + switch (integratedOutputType) + { + case IntegratedOutputType.AO: + lightmappingContext.IntegratorContext.LightmapAOIntegrator.Normalize(cmd, accumulatedOutput); + break; + case IntegratedOutputType.Direct: + lightmappingContext.IntegratorContext.LightmapDirectIntegrator.Normalize(cmd, accumulatedOutput); + break; + case IntegratedOutputType.Indirect: + lightmappingContext.IntegratorContext.LightmapIndirectIntegrator.Normalize(cmd, accumulatedOutput); + break; + case IntegratedOutputType.Validity: + lightmappingContext.IntegratorContext.LightmapValidityIntegrator.Normalize(cmd, accumulatedOutput); + break; + case IntegratedOutputType.ShadowMask: + lightmappingContext.IntegratorContext.LightmapShadowMaskIntegrator.Normalize(cmd, accumulatedOutput, lightmappingContext.AccumulatedDirectionalOutput); + break; + } + + // Make sure all the work is done before writing to disk + GraphicsHelpers.Flush(cmd); + + if (debugDispatches) + { + gpuSync.Sync(cmd); + Console.WriteLine($"Write {integratedOutputType} lightmap {lightmapIndex} to disk."); + //LightmapIntegrationHelpers.WriteRenderTexture(cmd, $"Temp/lm{lightmapIndex}_type{lightmapType}_final.r2dr", lightmappingContext.AccumulatedOutput, lightmappingContext.AccumulatedOutput.width, lightmappingContext.AccumulatedOutput.height); + } + + // Write lightmap to disk. TODO: kick off a C# job so the compression and file IO happens in a different thread LIGHT-1772 + bool outputPrimaryTexture = integratedRequestOutputType.Value.HasFlag(RequestedSubOutput.PrimaryTexture); + // We explicitly take into account whether to write for directionality, no directionality or for both. + if (outputPrimaryTexture) + if (LightmapIntegrationHelpers.WriteLightmap(cmd, accumulatedOutput, integratedOutputType, lightmapIndex, request.outputFolderPath) == false) + return Result.WriteToDiskFailure; + + if (!integratedRequestOutputType.Value.HasFlag(RequestedSubOutput.DirectionalityTexture)) + continue; + + // Write 'direct/indirect directionality' if requested - it is baked in the same pass as 'direct/indirect'. + IntegratedOutputType directionalityLightmapType = + integratedOutputType == IntegratedOutputType.Direct ? IntegratedOutputType.DirectionalityDirect : IntegratedOutputType.DirectionalityIndirect; + if (LightmapIntegrationHelpers.WriteLightmap(cmd, lightmappingContext.AccumulatedDirectionalOutput, directionalityLightmapType, lightmapIndex, request.outputFolderPath) == false) + return Result.WriteToDiskFailure; + } + + // Get rid of the normal output + if (normalBuffer is not null) + normalBuffer.Release(); + CoreUtils.Destroy(normalBuffer); + + // Write black outputs for the lightmap components we didn't bake. LightBaker does this too. + Texture2D blackOutput = new Texture2D(resolution, resolution, TextureFormat.RGBAFloat, false); + blackOutput.name = "BlackOutput"; + blackOutput.hideFlags = HideFlags.HideAndDontSave; + blackOutput.SetPixels(new Color[resolution * resolution]); + blackOutput.Apply(); + byte[] compressedBlack = blackOutput.EncodeToR2D(); + try + { + // Environment is part of indirect irradiance. + if (request.outputTypeMask.HasFlag(LightmapRequestOutputType.IrradianceEnvironment)) + File.WriteAllBytes(request.outputFolderPath + $"/irradianceEnvironment{lightmapIndex}.r2d", compressedBlack); + + // occupiedTexels is needed for analytics, don't fail the bake if we cannot write it. + UInt64 occupiedTexels = (UInt64)lightmapRequestData.atlassing.m_EstimatedTexelCount; + if (request.outputTypeMask.HasFlag(LightmapRequestOutputType.Occupancy)) + File.WriteAllBytes(request.outputFolderPath + $"/occupiedTexels{lightmapIndex}.UInt64", BitConverter.GetBytes(occupiedTexels)); + } + catch (Exception e) + { + CoreUtils.Destroy(blackOutput); + Debug.Assert(false, e.Message); + return Result.WriteToDiskFailure; + } + CoreUtils.Destroy(blackOutput); + } + } + return Result.Success; + } + + private static Dictionary GetRequestedIntegratedOutputTypes(in LightmapRequest request) + { + // TODO handle lightmapOffset and lightmapCount from the request. + // Turn the bits set in the request bitmask into an array of LightmapRequestOutputType. + HashSet lightmapRequestOutputTypes = BitfieldToList((int)request.outputTypeMask); + + // Make a dictionary of the lightmap types that are requested, the value indicates if directionality is requested for that stage (when it makes sense). + // This accounts for the fact that for direct and indirect directionality is baked in the same pass. But for purposes of deciding what lightmap output + // files to write we need to store that information as a bit flag. + Dictionary requestedLightmapTypes = new(); + void AddToRequestedLightmapTypes(IntegratedOutputType lightmapType, RequestedSubOutput type) + { + if (!requestedLightmapTypes.TryAdd(lightmapType, type)) + requestedLightmapTypes[lightmapType] |= type; + } + + foreach (var lightmapRequestOutputType in lightmapRequestOutputTypes) + { + if (!LightmapRequestOutputTypeToIntegratedOutputType(lightmapRequestOutputType, out IntegratedOutputType lightmapType)) + continue; + + switch (lightmapType) + { + case IntegratedOutputType.Indirect: + AddToRequestedLightmapTypes(IntegratedOutputType.Indirect, RequestedSubOutput.PrimaryTexture); + break; + case IntegratedOutputType.DirectionalityIndirect: + AddToRequestedLightmapTypes(IntegratedOutputType.Indirect, RequestedSubOutput.DirectionalityTexture); + break; + case IntegratedOutputType.Direct: + AddToRequestedLightmapTypes(IntegratedOutputType.Direct, RequestedSubOutput.PrimaryTexture); + break; + case IntegratedOutputType.DirectionalityDirect: + AddToRequestedLightmapTypes(IntegratedOutputType.Direct, RequestedSubOutput.DirectionalityTexture); + break; + default: + AddToRequestedLightmapTypes(lightmapType, RequestedSubOutput.PrimaryTexture); + break; + } + } + + return requestedLightmapTypes; + } + + private static HashSet GetRequestedNonIntegratedOutputTypes(in LightmapRequest request) + { + HashSet outputTypes = + BitfieldToList((int) (request.outputTypeMask & (LightmapRequestOutputType.Normal | LightmapRequestOutputType.Occupancy | LightmapRequestOutputType.ChartIndex | LightmapRequestOutputType.OverlapPixelIndex))); + bool needsNormalsForDirectionality = request.outputTypeMask.HasFlag(LightmapRequestOutputType.DirectionalityIndirect) || request.outputTypeMask.HasFlag(LightmapRequestOutputType.DirectionalityDirect); + if (needsNormalsForDirectionality) + outputTypes.Add(LightmapRequestOutputType.Normal); + + return outputTypes; + } + + private static (uint directSampleCount, uint effectiveIndirectSampleCount) GetProbeSampleCounts(in SampleCount probeSampleCounts) + { + uint indirectSampleCount = probeSampleCounts.indirectSampleCount; + + return (probeSampleCounts.directSampleCount, indirectSampleCount); + } + + internal static bool ExecuteProbeRequests(in BakeInput bakeInput, in ProbeRequestData probeRequestData, UnityComputeDeviceContext deviceContext, + bool useLegacyBakingBehavior, UnityComputeWorld world, uint bounceCount, BakeProgressState progressState, + UnityEngine.Rendering.Sampling.SamplingResources samplingResources) + { + if (probeRequestData.requests.Length == 0) + return true; + + ProbeIntegratorResources integrationResources = new(); + integrationResources.Load(world.RayTracingContext); + + var probeOcclusionLightIndexMappingShader = UnityEditor.AssetDatabase.LoadAssetAtPath("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute"); + using UnityComputeProbeIntegrator probeIntegrator = new(!useLegacyBakingBehavior, samplingResources, integrationResources, probeOcclusionLightIndexMappingShader); + probeIntegrator.SetProgressReporter(progressState); + + // Create input position buffer + using NativeArray inputPositions = new(probeRequestData.positions, Allocator.Temp); + var positionsBuffer = deviceContext.CreateBuffer((ulong)probeRequestData.positions.Length, sizeof(float) * 3); + BufferSlice positionsBufferSlice = positionsBuffer.Slice(); + var positionsWriteEvent = deviceContext.CreateEvent(); + deviceContext.WriteBuffer(positionsBufferSlice, inputPositions, positionsWriteEvent); + deviceContext.DestroyEvent(positionsWriteEvent); + + // Create input probe occlusion light indices buffer (shared for all requests) + using NativeArray inputPerProbeLightIndices = new(probeRequestData.occlusionLightIndices, Allocator.Temp); + var perProbeLightIndicesBuffer = deviceContext.CreateBuffer((ulong)probeRequestData.occlusionLightIndices.Length, sizeof(int)); + BufferSlice perProbeLightIndicesBufferSlice = perProbeLightIndicesBuffer.Slice(); + deviceContext.WriteBuffer(perProbeLightIndicesBufferSlice, inputPerProbeLightIndices); + + (uint directSampleCount, uint effectiveIndirectSampleCount) = GetProbeSampleCounts(bakeInput.lightingSettings.probeSampleCounts); + + ProbeRequest[] probeRequests = probeRequestData.requests; + for (int probeRequestIndex = 0; probeRequestIndex < probeRequests.Length; probeRequestIndex++) + { + // Read data from request and prepare integrator + ref readonly ProbeRequest request = ref probeRequests[probeRequestIndex]; + int requestOffset = (int)request.positionOffset; + int requestLength = (int)request.count; + ulong floatBufferSize = sizeof(float) * request.count; + float pushoff = request.pushoff; + probeIntegrator.Prepare(deviceContext, world, positionsBuffer.Slice(), pushoff, (int)bounceCount); + + List eventsToWaitFor = new(); + List buffersToDestroy = new(); + + // Integrate indirect radiance + using NativeArray outputIndirectRadiance = new(requestLength, Allocator.Persistent); + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceIndirect)) + { + Debug.Assert(effectiveIndirectSampleCount > 0); + var shIndirectBuffer = deviceContext.CreateBuffer(request.count * 27, sizeof(float)); + buffersToDestroy.Add(shIndirectBuffer); + var shIndirectBufferSlice = shIndirectBuffer.Slice(); + var integrationResult = probeIntegrator.IntegrateIndirectRadiance(deviceContext, requestOffset, requestLength, + (int)effectiveIndirectSampleCount, request.ignoreIndirectEnvironment, shIndirectBufferSlice); + Assert.AreEqual(IProbeIntegrator.ResultType.Success, integrationResult.type, "IntegrateIndirectRadiance failed."); + var readEvent = deviceContext.CreateEvent(); + deviceContext.ReadBuffer(shIndirectBufferSlice, outputIndirectRadiance, readEvent); + eventsToWaitFor.Add(readEvent); + } + + // Integrate direct radiance + using NativeArray outputDirectRadiance = new(requestLength, Allocator.Persistent); + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceDirect)) + { + Debug.Assert(directSampleCount > 0); + var shDirectBuffer = deviceContext.CreateBuffer(request.count * 27, sizeof(float)); + buffersToDestroy.Add(shDirectBuffer); + var shDirectBufferSlice = shDirectBuffer.Slice(); + var integrationResult = probeIntegrator.IntegrateDirectRadiance(deviceContext, requestOffset, requestLength, + (int)directSampleCount, request.ignoreDirectEnvironment, shDirectBufferSlice); + Assert.AreEqual(IProbeIntegrator.ResultType.Success, integrationResult.type, "IntegrateDirectRadiance failed."); + var readEvent = deviceContext.CreateEvent(); + deviceContext.ReadBuffer(shDirectBufferSlice, outputDirectRadiance, readEvent); + eventsToWaitFor.Add(readEvent); + } + + // Integrate validity + using NativeArray outputValidity = new(requestLength, Allocator.Persistent); + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.Validity)) + { + Debug.Assert(effectiveIndirectSampleCount > 0); + var validityBuffer = deviceContext.CreateBuffer(request.count, sizeof(float)); + buffersToDestroy.Add(validityBuffer); + var validityBufferSlice = validityBuffer.Slice(); + var integrationResult = probeIntegrator.IntegrateValidity(deviceContext, requestOffset, requestLength, + (int)effectiveIndirectSampleCount, validityBufferSlice); + Assert.AreEqual(IProbeIntegrator.ResultType.Success, integrationResult.type, "IntegrateValidity failed."); + var readEvent = deviceContext.CreateEvent(); + deviceContext.ReadBuffer(validityBufferSlice, outputValidity, readEvent); + eventsToWaitFor.Add(readEvent); + } + + // Integrate occlusion values + const int maxOcclusionLightsPerProbe = 4; + bool usesProbeOcclusion = bakeInput.lightingSettings.mixedLightingMode != MixedLightingMode.IndirectOnly; + using NativeArray outputOcclusion = new(requestLength * maxOcclusionLightsPerProbe, Allocator.Persistent, NativeArrayOptions.ClearMemory); + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.LightProbeOcclusion) && usesProbeOcclusion) + { + var occlusionBuffer = deviceContext.CreateBuffer(maxOcclusionLightsPerProbe * request.count, sizeof(float)); + buffersToDestroy.Add(occlusionBuffer); + var occlusionBufferSlice = occlusionBuffer.Slice(); + + var integrationResult = probeIntegrator.IntegrateOcclusion(deviceContext, requestOffset, requestLength, + (int)effectiveIndirectSampleCount, maxOcclusionLightsPerProbe, perProbeLightIndicesBufferSlice, occlusionBufferSlice); + Assert.AreEqual(IProbeIntegrator.ResultType.Success, integrationResult.type, "IntegrateOcclusion failed."); + + EventID readEvent = deviceContext.CreateEvent(); + deviceContext.ReadBuffer(occlusionBufferSlice, outputOcclusion, readEvent); + eventsToWaitFor.Add(readEvent); + } + + // Gather occlusion containers / indices + var outputOcclusionIndices = new LightProbeOcclusion[requestLength]; + for (int probeIdx = 0; probeIdx < requestLength; probeIdx++) + { + ref var occlusion = ref outputOcclusionIndices[probeIdx]; + occlusion.SetDefaultValues(); + } + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.MixedLightOcclusion) && usesProbeOcclusion) + { + // Build output data + for (int probeIdx = 0; probeIdx < requestLength; probeIdx++) + { + LightProbeOcclusion occlusion = new(); + occlusion.SetDefaultValues(); + for (int indirectLightIdx = 0; indirectLightIdx < maxOcclusionLightsPerProbe; indirectLightIdx++) + { + int bakeInputLightIdx = inputPerProbeLightIndices[probeIdx * maxOcclusionLightsPerProbe + indirectLightIdx]; + if (bakeInputLightIdx >= 0) + { + sbyte occlusionMaskChannel = (sbyte)bakeInput.lightData[bakeInputLightIdx].shadowMaskChannel; + + occlusion.SetProbeOcclusionLightIndex(indirectLightIdx, bakeInputLightIdx); + occlusion.SetOcclusion(indirectLightIdx, 0.0f); + occlusion.SetOcclusionMaskChannel(indirectLightIdx, occlusionMaskChannel); + } + } + outputOcclusionIndices[probeIdx] = occlusion; + } + } + + // Flush and wait for all events to complete + deviceContext.Flush(); + foreach (var evt in eventsToWaitFor) + { + bool ok = deviceContext.Wait(evt); + Assert.IsTrue(ok); + deviceContext.DestroyEvent(evt); + } + + // Cleanup temporary buffers + foreach (var buffer in buffersToDestroy) + { + deviceContext.DestroyBuffer(buffer); + } + + // Write output data to disk + // Write light probe data to disk, so the C++ side post processing stage can pick it up. + // We can use the C# side post processing API in the future, once we can write the LDA from C#. + string path = request.outputFolderPath; + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceDirect)) + { + string filePath = path; + filePath += string.Format("/radianceDirect{0}.shl2", probeRequestIndex); + if (!SaveProbesToFileInterleaved(filePath, outputDirectRadiance)) + return false; + } + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.RadianceIndirect)) + { + string filePath = path; + filePath += string.Format("/radianceIndirect{0}.shl2", probeRequestIndex); + if (!SaveProbesToFileInterleaved(filePath, outputIndirectRadiance)) + return false; + } + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.Validity)) + { + string filePath = path; + filePath += string.Format("/validity{0}.float", probeRequestIndex); + if (!SaveArrayToFile(filePath, requestLength, outputValidity.ToArray())) + return false; + } + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.LightProbeOcclusion)) + { + string filePath = path; + filePath += string.Format("/lightProbeOcclusion{0}.vec4", probeRequestIndex); + if (!SaveArrayToFile(filePath, requestLength, outputOcclusion.ToArray())) + return false; + } + if (request.outputTypeMask.HasFlag(ProbeRequestOutputType.MixedLightOcclusion)) + { + // TODO(pema.malling): dummy data, the C++ side post processing stage needs this. + // https://jira.unity3d.com/browse/LIGHT-1102 + string filePath = path; + filePath += string.Format("/mixedLightOcclusion{0}.occ", probeRequestIndex); + if (!SaveArrayToFile(filePath, requestLength, outputOcclusionIndices)) + return false; + } + } + + // Cleanup created buffers + deviceContext.DestroyBuffer(positionsBuffer); + deviceContext.DestroyBuffer(perProbeLightIndicesBuffer); + + return true; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs.meta new file mode 100644 index 00000000000..d10c63335f5 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerStrangler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4d8223d7da5fa45419aef4292e4fc672 diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs new file mode 100644 index 00000000000..dd1ac2d858a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs @@ -0,0 +1,116 @@ +using System.IO; +using System.Threading; +using UnityEditor.AssetImporters; +using UnityEditor.Experimental; +using UnityEditor.LightBaking; +using UnityEngine; +using UnityEngine.LightTransport; +using static UnityEditor.LightBaking.LightBaker; + +namespace UnityEditor.PathTracing.LightBakerBridge +{ + [ScriptedImporter(version: 1, ext: "ext_unused_lightbaker")] + internal class LightBakerWorkerProcessImporter : ScriptedImporter + { + private const string LightBakerWorkerProcess = "[LightBaker worker process] "; + private const string BakeAssetFolder = "Temp/LightBakerAssetImport"; + + internal static bool BakeWithWorkerProcess(string bakeInputPath, string lightmapRequestsPath, + string lightProbeRequestsPath, string bakeOutputFolderPath, int progressPort) + { + // Setup hidden import folder. + if (!AssetDatabase.AssetPathExists(BakeAssetFolder)) + Directory.CreateDirectory(BakeAssetFolder); + + var assetPath = $"{BakeAssetFolder}/{nameof(BakeImport)}.asset"; + + // Write the 'recipe' asset. + var importAsset = ScriptableObject.CreateInstance(); + importAsset.name = nameof(BakeImport); + importAsset.BakeInputPath = bakeInputPath; + importAsset.LightmapRequestsPath = lightmapRequestsPath; + importAsset.LightProbeRequestsPath = lightProbeRequestsPath; + importAsset.BakeOutputFolderPath = bakeOutputFolderPath; + importAsset.ProgressPort = progressPort; + + // Import it. + var guid = GUID.Generate(); + UnityEditorInternal.InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { importAsset }, assetPath, true); + File.WriteAllText(assetPath + ".meta", $"fileFormatVersion: 2\nguid: {guid}\nDefaultImporter:\n externalObjects: {{}}\n assetBundleName:\n assetBundleVariant:\n"); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + + // Generate an artifact asynchronously, kicking off the bake. + var headKey = new ArtifactKey(guid, typeof(LightBakerWorkerProcessImporter)); + AssetDatabaseExperimental.ProduceArtifactAsync(headKey); + + return true; + } + + public override void OnImportAsset(AssetImportContext ctx) + { + CancellationTokenSource bakeIsFinished = new(); + ExternalProcessConnection progressConnection = new(); + BakeProgressState progressState = new(); + + using (progressConnection) + using (bakeIsFinished) + using (progressState) + { + try + { + var bakeImport = AssetDatabase.LoadAssetAtPath(ctx.assetPath); + if (bakeImport == null) + { + Debug.LogError($"{LightBakerWorkerProcess}Failed to load asset at path: {ctx.assetPath}."); + return; + } + + // If the Editor is waiting for us to connect, it will time out if we cannot connect. + if (!progressConnection.Connect(bakeImport.ProgressPort)) + { + Debug.LogError($"{LightBakerWorkerProcess}Failed to connect to the parent process on port '{bakeImport.ProgressPort}'."); + return; + } + + // Bake while reporting progress. + Thread progressReporterThread = new(() => ProgressReporterThreadFunction(progressConnection, bakeIsFinished.Token, progressState)); + progressReporterThread.Start(); + + bool bakeOk = LightBakerStrangler.Bake(bakeImport.BakeInputPath, bakeImport.LightmapRequestsPath, bakeImport.LightProbeRequestsPath, bakeImport.BakeOutputFolderPath, progressState); + + // Stop the progress thread and wait for the thread. + bakeIsFinished.Cancel(); + progressReporterThread.Join(); + + ReportResultToParentProcess( + bakeOk + ? new Result { type = ResultType.Success, message = "Success." } + : new Result { type = ResultType.JobFailed, message = "LightBakerStrangler.Bake failed." }, progressConnection); + } + finally + { + // Stop the progress thread. + bakeIsFinished.Cancel(); + } + } + } + + private static void ProgressReporterThreadFunction(ExternalProcessConnection connection, CancellationToken bakeIsFinished, BakeProgressState progressState) + { + if (connection == null) + return; + const int waitBetweenProgressReportsMs = 100; + while (!progressState.WasCancelled() && !bakeIsFinished.IsCancellationRequested) + { + bool cancellationRequested = LightBaker.GetCancellationRequested(connection); + if (cancellationRequested) + progressState.Cancel(); + + float progress = progressState.Progress(); + ReportProgressToParentProcess(progress, connection); + + Thread.Sleep(waitBetweenProgressReportsMs); + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs.meta new file mode 100644 index 00000000000..65ca9d4bea4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/LightBakerWorkerProcessImporter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59cb2244d87edf44a8800d2a01fc1dfd diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef new file mode 100644 index 00000000000..3e969358cc3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef @@ -0,0 +1,21 @@ +{ + "name": "Unity.PathTracing.Editor", + "rootNamespace": "", + "references": [ + "GUID:214c0945bb158c940aada223f3223ee8", + "GUID:c49c619b6af2be941af9bcbca2641964", + "GUID:df380645f10b7bc4b97d4f5eb6303d95", + "GUID:d8b63aba1907145bea998dd612889d6b" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef.meta new file mode 100644 index 00000000000..315d0743dab --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/Unity.PathTracing.Editor.asmdef.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 9149a3cbb036049f68e4e5b6388b9c85 +AssemblyDefinitionImporter: + externalObjects: {} + userData: diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs new file mode 100644 index 00000000000..2088fad34a3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs @@ -0,0 +1,124 @@ +using UnityEngine.PathTracing.Core; +using UnityEngine.LightTransport; +using UnityEngine.LightTransport.PostProcessing; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.PostProcessing +{ + // UnityComputeProbePostProcessor was temporarily moved to the Editor assembly because of its unfortunate + // dependency on RadeonRaysProbePostProcessor.DeringSphericalHarmonicsL2 which is editor-only. + // We should move it back to Runtime: https://jira.unity3d.com/browse/GFXFEAT-1035. + internal class UnityComputeProbePostProcessor : IProbePostProcessor + { + ProbePostProcessor _probePostProcessor; + + /// + /// UnityComputeProbePostProcessor constructor. + /// Expects to be given the "ProbePostProcessing.compute" shader. + /// + /// The compute shader containing post processing kernels. Should be the "ProbePostProcessing.compute" shader. + public UnityComputeProbePostProcessor(ComputeShader computeShader) + { + _probePostProcessor = new ProbePostProcessor(); + _probePostProcessor.Prepare(computeShader); + } + + public bool Initialize(IDeviceContext context) + { + return true; + } + + public void Dispose() + { + } + + public bool AddSphericalHarmonicsL2(IDeviceContext context, BufferSlice a, BufferSlice b, BufferSlice sum, int probeCount) + { + var unityCtx = context as UnityComputeDeviceContext; + Debug.Assert(unityCtx != null); + + _probePostProcessor.AddSphericalHarmonicsL2( + unityCtx.GetCommandBuffer(), + unityCtx.GetComputeBuffer(a.Id), + unityCtx.GetComputeBuffer(b.Id), + unityCtx.GetComputeBuffer(sum.Id), + (uint)a.Offset, + (uint)b.Offset, + (uint)sum.Offset, + (uint)probeCount); + + return true; + } + + public bool ConvertToUnityFormat(IDeviceContext context, BufferSlice irradianceIn, BufferSlice irradianceOut, int probeCount) + { + var unityCtx = context as UnityComputeDeviceContext; + Debug.Assert(unityCtx != null); + + _probePostProcessor.ConvertToUnityFormat( + unityCtx.GetCommandBuffer(), + unityCtx.GetComputeBuffer(irradianceIn.Id), + unityCtx.GetComputeBuffer(irradianceOut.Id), + (uint)irradianceIn.Offset, + (uint)irradianceOut.Offset, + (uint)probeCount); + + return true; + } + + public bool ConvolveRadianceToIrradiance(IDeviceContext context, BufferSlice radianceIn, BufferSlice irradianceOut, int probeCount) + { + var unityCtx = context as UnityComputeDeviceContext; + Debug.Assert(unityCtx != null); + + _probePostProcessor.ConvolveRadianceToIrradiance( + unityCtx.GetCommandBuffer(), + unityCtx.GetComputeBuffer(radianceIn.Id), + unityCtx.GetComputeBuffer(irradianceOut.Id), + (uint)radianceIn.Offset, + (uint)irradianceOut.Offset, + (uint)probeCount); + + return true; + } + + public bool ScaleSphericalHarmonicsL2(IDeviceContext context, BufferSlice shIn, BufferSlice shOut, int probeCount, float scale) + { + var unityCtx = context as UnityComputeDeviceContext; + Debug.Assert(unityCtx != null); + + _probePostProcessor.ScaleSphericalHarmonicsL2( + unityCtx.GetCommandBuffer(), + unityCtx.GetComputeBuffer(shIn.Id), + unityCtx.GetComputeBuffer(shOut.Id), + (uint)shIn.Offset, + (uint)shOut.Offset, + (uint)probeCount, + scale); + + return true; + } + + public bool WindowSphericalHarmonicsL2(IDeviceContext context, BufferSlice shIn, BufferSlice shOut, int probeCount) + { + var unityCtx = context as UnityComputeDeviceContext; + Debug.Assert(unityCtx != null); + + _probePostProcessor.WindowSphericalHarmonicsL2( + unityCtx.GetCommandBuffer(), + unityCtx.GetComputeBuffer(shIn.Id), + unityCtx.GetComputeBuffer(shOut.Id), + (uint)shIn.Offset, + (uint)shOut.Offset, + (uint)probeCount); + + return true; + } + + public bool DeringSphericalHarmonicsL2(IDeviceContext context, BufferSlice shIn, BufferSlice shOut, int probeCount) + { + using var radeonRaysProbePostProcessor = new RadeonRaysProbePostProcessor(); + return radeonRaysProbePostProcessor.DeringSphericalHarmonicsL2(context, shIn, shOut, probeCount); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs.meta new file mode 100644 index 00000000000..d0497845de7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/PathTracing/UnityComputeProbePostProcessor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 33ed5304014ba4c0c9d86740f0d8ab49 diff --git a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphTestsCore.cs b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphTestsCore.cs index 3a6bcde82f7..9602d17ebc2 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphTestsCore.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphTestsCore.cs @@ -37,6 +37,10 @@ internal class RenderGraphTestPipelineAsset : RenderPipelineAsset evt) autoPlayToggle.text = evt.newValue ? L10n.Tr("Auto Update") : L10n.Tr("Pause"); m_Paused = evt.newValue; + if (!m_Paused && !m_IsDeviceConnected && m_ConnectedDeviceName != k_EditorName) + { + ConnectDebugSession(); + } + UpdateStatusLabel(); // Force update when unpausing @@ -2100,7 +2105,7 @@ void CreateGUI() else { m_ConnectedDeviceName = k_EditorName; - m_IsDeviceConnected = false; + m_IsDeviceConnected = true; } connectionState.Dispose(); // Dispose it immediately after use @@ -2157,7 +2162,14 @@ void OnPlayerDisconnected(int playerID) } } - ConnectDebugSession(); + if (!m_Paused) + { + ConnectDebugSession(); + } + else + { + UpdateStatusLabel(); + } } internal void ConnectDebugSession() @@ -2165,7 +2177,7 @@ internal void ConnectDebugSession() { if (typeof(TSession) == typeof(RenderGraphEditorLocalDebugSession)) { - if (!m_IsDeviceConnected) + if (m_ConnectedDeviceName == "Unknown" || m_ConnectedDeviceName == k_EditorName) { m_ConnectedDeviceName = k_EditorName; m_IsDeviceConnected = true; diff --git a/Packages/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef b/Packages/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef index 7663d4b4ab4..ded5447b817 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef +++ b/Packages/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef @@ -23,6 +23,11 @@ "name": "com.unity.burst", "expression": "1.5.0", "define": "HAS_BURST" + }, + { + "name": "com.unity.modules.physics", + "expression": "1.0.0", + "define": "ENABLE_PHYSICS_MODULE" } ], "noEngineReferences": false diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling.meta b/Packages/com.unity.render-pipelines.core/Editor/Upscaling.meta new file mode 100644 index 00000000000..ba73323abaa --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dfe03cc61c27c974ab28f8691b3f8c0f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs new file mode 100644 index 00000000000..dbdb768474e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs @@ -0,0 +1,167 @@ +#if UNITY_EDITOR +#if ENABLE_UPSCALER_FRAMEWORK && ENABLE_NVIDIA +using UnityEditor; +using UnityEngine; +using UnityEngine.NVIDIA; + +[CustomEditor(typeof(DLSSOptions))] +public class DLSSOptionsEditor : Editor +{ + // Declare variables to hold each property + private SerializedProperty m_QualityMode; + private SerializedProperty m_FixedResolution; + private SerializedProperty m_PresetQuality; + private SerializedProperty m_PresetBalanced; + private SerializedProperty m_PresetPerformance; + private SerializedProperty m_PresetUltraPerformance; + private SerializedProperty m_PresetDLAA; + private void OnEnable() + { + // Find each property by its exact field name in DLSSOptions.cs + m_QualityMode = serializedObject.FindProperty("DLSSQualityMode"); + m_FixedResolution = serializedObject.FindProperty("FixedResolutionMode"); + m_PresetQuality = serializedObject.FindProperty("DLSSRenderPresetQuality"); + m_PresetBalanced = serializedObject.FindProperty("DLSSRenderPresetBalanced"); + m_PresetPerformance = serializedObject.FindProperty("DLSSRenderPresetPerformance"); + m_PresetUltraPerformance = serializedObject.FindProperty("DLSSRenderPresetUltraPerformance"); + m_PresetDLAA = serializedObject.FindProperty("DLSSRenderPresetDLAA"); + } + + #region STYLES + private static readonly string[] DLSSPerfQualityLabels = + { // should follow enum value ordering in DLSSQuality enum + DLSSQuality.MaximumPerformance.ToString(), + DLSSQuality.Balanced.ToString(), + DLSSQuality.MaximumQuality.ToString(), + DLSSQuality.UltraPerformance.ToString(), + DLSSQuality.DLAA.ToString() + }; + private static string[][] DLSSPresetOptionsForEachPerfQuality = PopulateDLSSQualityPresetLabels(); + private static string[][] PopulateDLSSQualityPresetLabels() + { + int CountBits(uint bitMask) // System.Numerics.BitOperations not available + { + int count = 0; + while (bitMask > 0) + { + count += (bitMask & 1) > 0 ? 1 : 0; + bitMask >>= 1; + } + return count; + } + + System.Array perfQualities = System.Enum.GetValues(typeof(DLSSQuality)); + string[][] labels = new string[perfQualities.Length][]; + foreach (DLSSQuality quality in perfQualities) + { + uint presetBitmask = GraphicsDevice.GetAvailableDLSSPresetsForQuality(quality); + int numPresets = CountBits(presetBitmask) + 1; // +1 for default option which is available to all quality enums + labels[(int)quality] = new string[numPresets]; + + int iWrite = 0; + System.Array presets = System.Enum.GetValues(typeof(DLSSPreset)); + foreach (DLSSPreset preset in presets) + { + if (preset == DLSSPreset.Preset_Default) + { + labels[(int)quality][iWrite++] = "Default Preset"; + continue; + } + + if ((presetBitmask & (uint)preset) != 0) + { + string presetName = preset.ToString().Replace('_', ' '); + labels[(int)quality][iWrite++] = presetName + " - " + GraphicsDevice.GetDLSSPresetExplanation(preset); + } + } + } + return labels; + } + + private static readonly GUIContent renderPresetsLabel = new GUIContent("Render Presets", "Selects an internal DLSS tuning profile. Presets adjust reconstruction behavior, trading off between sharpness, stability, and performance. Different presets may work better depending on scene content and motion."); + #endregion + + + // DLSSOptions contain DLSS presets for each quality mode. + // The presets available for each quality mode may be different from one another and change over time between DLSS releases. + // E.g. DLAAPreset = { F, J, K } + // BalancedPreset = { J, K } + // Instead of letting Unity default-render the GUI, we write our own inspector logic to enforce the preset value requirements. + void DrawPresetDropdown(ref SerializedProperty presetProp, DLSSQuality perfQuality) + { + // each DLSSQuality has a different set of DLSSPresets, represented by a bitmask. + uint presetBitmask = GraphicsDevice.GetAvailableDLSSPresetsForQuality(perfQuality); + bool propHasInvalidPresetValue = presetProp.uintValue != 0 && (presetBitmask & presetProp.uintValue) == 0; + if (propHasInvalidPresetValue) + { + Debug.LogWarningFormat("DLSS Preset {0} not found for quality setting {1}, resetting to default value.", + ((DLSSPreset)presetProp.uintValue).ToString(), + perfQuality.ToString() + ); + presetProp.uintValue = 0; + } + + // We don't want to deal with List & using bitmasks, + // so we need some bit ops to convert between GUI index <--> Preset value + int FindPresetGUIIndex(uint presetBitmask, uint presetValue) + { + int i = 0; + while (presetValue > 0) + { + i += (presetBitmask & 1) > 0 ? 1 : 0; + presetBitmask >>= 1; + presetValue >>= 1; + } + return i; // includes 0=default, goes like 1=preset_A, 2=preset_B ... + } + uint GUIIndexToPresetValue(uint presetBitmask, uint index) + { + // e.g. bitset: 100101 --> 3 bits set, supports 4 presets (0=default, +3 other presets). + // ^ i = 1 -> Preset A = 1 + // ^ i = 2 -> Preset C = 4 + // ^ i = 3 -> Preset F = 32 + uint val = 0; + while (index > 0 && presetBitmask > 0) + { + if ((presetBitmask & 1) != 0) + --index; + presetBitmask >>= 1; + val = val == 0 ? 1 : (val << 1); + } + if (index != 0) + { + Debug.LogWarningFormat("DLSSPreset (index={0}) not found in the supported preset list (mask={1}), setting to default value.", index, presetBitmask); + return 0; + } + // Debug.LogFormat("Setting preset {0} : {1}", ((DLSSPreset)val).ToString(), val); + return val; + } + + int presetIndex = FindPresetGUIIndex(presetBitmask, presetProp.uintValue); + int iNew = EditorGUILayout.Popup(DLSSPerfQualityLabels[(int)perfQuality], presetIndex, DLSSPresetOptionsForEachPerfQuality[(int)perfQuality]); + if (iNew != presetIndex) + presetProp.uintValue = GUIIndexToPresetValue(presetBitmask, (uint)iNew); + } + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.PropertyField(m_QualityMode); + EditorGUILayout.PropertyField(m_FixedResolution); + + EditorGUILayout.LabelField(renderPresetsLabel, EditorStyles.boldLabel); + ++EditorGUI.indentLevel; + + DrawPresetDropdown(ref m_PresetQuality, DLSSQuality.MaximumQuality); + DrawPresetDropdown(ref m_PresetBalanced, DLSSQuality.Balanced); + DrawPresetDropdown(ref m_PresetPerformance, DLSSQuality.MaximumPerformance); + DrawPresetDropdown(ref m_PresetUltraPerformance, DLSSQuality.UltraPerformance); + DrawPresetDropdown(ref m_PresetDLAA, DLSSQuality.DLAA); + + --EditorGUI.indentLevel; + + serializedObject.ApplyModifiedProperties(); + } +} +#endif +#endif diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs.meta new file mode 100644 index 00000000000..a6c2860babc --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/DLSSOptionsEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3d45844b7594ca649b150d24562f5429 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs new file mode 100644 index 00000000000..df858ac9e4c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs @@ -0,0 +1,20 @@ +#if ENABLE_UPSCALER_FRAMEWORK +#if UNITY_EDITOR +using UnityEditor; + +/// +/// This custom editor ensures that when drawing any UpscalerOptions object, +/// the default "Script" field is not shown. Applies to all derived options. +/// +[CustomEditor(typeof(UnityEngine.Rendering.UpscalerOptions), true)] +public class UpscalerOptionsEditor : Editor +{ + public override void OnInspectorGUI() + { + // DrawDefaultInspector renders all serialized fields except for the "Script" field + // and any fields marked with [HideInInspector]. + DrawDefaultInspector(); + } +} +#endif +#endif diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs.meta new file mode 100644 index 00000000000..9ae0ab7a6b1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b9e58b38929a83b4aa0fd7b0d0144014 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs new file mode 100644 index 00000000000..85bef5d585c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs @@ -0,0 +1,52 @@ +#if ENABLE_UPSCALER_FRAMEWORK +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; + +namespace UnityEngine.Rendering +{ +#nullable enable + /// + /// Manages the lifecycle of cached editors for UpscalerOptions ScriptableObjects. + /// + public class UpscalerOptionsEditorCache + { + private readonly Dictionary m_EditorCache = new Dictionary(); + + /// + /// Gets a cached editor for the given @options. If the editor is not + /// found in the cache, a new editor is created and then cached. + /// + public Editor? GetOrCreateEditor(ScriptableObject options) + { + if (options == null) + return null; + + if (!m_EditorCache.TryGetValue(options, out var editor) || editor == null) + { + editor = Editor.CreateEditor(options); + m_EditorCache[options] = editor; + } + + return editor; + } + + /// + /// Destroys all cached editor instances. + /// Call this from the parent editor's OnDisable method. + /// + public void Cleanup() + { + foreach (var editor in m_EditorCache.Values) + { + if (editor != null) + Object.DestroyImmediate(editor); + } + m_EditorCache.Clear(); + } + } + +#nullable disable +} +#endif +#endif // ENABLE_UPSCALER_FRAMEWORK diff --git a/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs.meta b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs.meta new file mode 100644 index 00000000000..14580aa456a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Editor/Upscaling/UpscalerOptionsEditorCache.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b35dd4b6f122695459036b125fcb7b48 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs deleted file mode 100644 index 8f0d580edfa..00000000000 --- a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Rendering; - -namespace UnityEditor.Rendering -{ - /// - /// Interface to add additional gizmo renders for a - /// - public interface IVolumeAdditionalGizmo - { - /// - /// The type that overrides this additional gizmo - /// - Type type { get; } - - /// - /// Additional gizmo draw for - /// - /// The - /// The - void OnBoxColliderDraw(IVolume scr, BoxCollider c); - - /// - /// Additional gizmo draw for - /// - /// The - /// The - void OnSphereColliderDraw(IVolume scr, SphereCollider c); - - /// - /// Additional gizmo draw for - /// - /// The - /// The - void OnMeshColliderDraw(IVolume scr, MeshCollider c); - } -} diff --git a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeEditor.cs b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeEditor.cs index a6cedcc6e5c..445bb71c539 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeEditor.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeEditor.cs @@ -24,6 +24,8 @@ static class Styles public static readonly GUIContent meshBoxCollider = EditorGUIUtility.TrTextContent("Add a Mesh Collider"); public static readonly GUIContent addColliderFixMessage = EditorGUIUtility.TrTextContentWithIcon("Add a Collider to this GameObject to set boundaries for the local Volume.", CoreEditorStyles.iconWarn); public static readonly GUIContent disableColliderFixMessage = EditorGUIUtility.TrTextContentWithIcon("Global Volumes do not need a collider. Disable or remove the collider.", CoreEditorStyles.iconWarn); + public static readonly GUIContent physicsBackendDisabledMessage = EditorGUIUtility.TrTextContentWithIcon("Local Volumes are unavailable when the Physics GameObject SDK is set to None. Either choose a different Physics SDK in Project Settings > Physics, or set the Mode to Global.", CoreEditorStyles.iconWarn); + public static readonly GUIContent physicsModuleDisabledMessage = EditorGUIUtility.TrTextContentWithIcon("Local Volumes are unavailable without the Physics module. Enable the Physics module, or set the Mode to Global.", CoreEditorStyles.iconWarn); public static readonly GUIContent enableColliderFixMessage = EditorGUIUtility.TrTextContentWithIcon("Local Volumes need a collider enabled. Enable the collider.", CoreEditorStyles.iconWarn); public static readonly GUIContent newLabel = EditorGUIUtility.TrTextContent("New", "Create a new profile."); public static readonly GUIContent saveLabel = EditorGUIUtility.TrTextContent("Save", "Save the instantiated profile"); @@ -92,6 +94,13 @@ public override VisualElement CreateInspectorGUI() } else { +#if ENABLE_PHYSICS_MODULE + bool physicsBackendIsNone = Physics.GetCurrentIntegrationInfo().isFallback; + if (physicsBackendIsNone) + EditorGUILayout.HelpBox(Styles.physicsBackendDisabledMessage); +#else + EditorGUILayout.HelpBox(Styles.physicsModuleDisabledMessage); +#endif if (hasCollider) { if (!collider.enabled) diff --git a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeGizmoDrawer.cs b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeGizmoDrawer.cs index b6daaa47b34..4e05d6dabe1 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeGizmoDrawer.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeGizmoDrawer.cs @@ -1,130 +1,103 @@ -using System; -using System.Collections.Generic; -using System.Reflection; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { - class VolumeGizmoDrawer + /// + /// Collection of static methods to draw gizmos for volume colliders. + /// + public static class VolumeGizmoDrawer { - #region GizmoCallbacks - static readonly Dictionary s_AdditionalGizmoCallbacks = new(); - - [InitializeOnLoadMethod] - static void InitVolumeGizmoCallbacks() + /// + /// Draws a box collider gizmo. + /// + /// Transform of the box collider. + /// Center position of the box collider. + /// Size of the box collider. + public static void DrawBoxCollider(Transform transform, Vector3 center, Vector3 size) { - foreach (var additionalGizmoCallback in TypeCache.GetTypesDerivedFrom()) - { - if (additionalGizmoCallback.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null) == null) - continue; - var instance = Activator.CreateInstance(additionalGizmoCallback) as IVolumeAdditionalGizmo; - s_AdditionalGizmoCallbacks.Add(instance.type, instance); - } + Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale); + + if (VolumesPreferences.drawWireFrame) + Gizmos.DrawWireCube(center, size); + if (VolumesPreferences.drawSolid) + Gizmos.DrawCube(center, size); } - #endregion + /// + /// Draws a sphere collider gizmo. + /// + /// Transform of the sphere collider. + /// Center position of the sphere collider. + /// Radius of the sphere collider. + public static void DrawSphereCollider(Transform transform, Vector3 center, float radius) + { + // For sphere the only scale that is used is the transform.x + Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one * transform.lossyScale.x); - [DrawGizmo(GizmoType.Active | GizmoType.Selected | GizmoType.NonSelected)] - static void OnDrawGizmos(IVolume scr, GizmoType gizmoType) + if (VolumesPreferences.drawWireFrame) + Gizmos.DrawWireSphere(center, radius); + if (VolumesPreferences.drawSolid) + Gizmos.DrawSphere(center, radius); + } + + /// + /// Draws a mesh collider gizmo. + /// + /// Transform of the mesh collider. + /// Mesh to draw. + public static void DrawMeshCollider(Transform transform, Mesh mesh) { - if (scr is not MonoBehaviour monoBehaviour) - return; + // We'll just use scaling as an approximation for volume skin. It's far from being + // correct (and is completely wrong in some cases). Ultimately we'd use a distance + // field or at least a tessellate + push modifier on the collider's mesh to get a + // better approximation, but the current Gizmo system is a bit limited and because + // everything is dynamic in Unity and can be changed at anytime, it's hard to keep + // track of changes in an elegant way (which we'd need to implement a nice cache + // system for generated volume meshes). - if (!monoBehaviour.enabled) - return; + Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale); - if (scr.isGlobal || scr.colliders == null) - return; + if (VolumesPreferences.drawWireFrame) + Gizmos.DrawWireMesh(mesh); + if (VolumesPreferences.drawSolid) + Gizmos.DrawMesh(mesh); // Mesh pivot should be centered or this won't work + } - // Store the computation of the lossyScale - var lossyScale = monoBehaviour.transform.lossyScale; - Gizmos.matrix = Matrix4x4.TRS(monoBehaviour.transform.position, monoBehaviour.transform.rotation, lossyScale); +#if ENABLE_PHYSICS_MODULE + [DrawGizmo(GizmoType.Active | GizmoType.Selected | GizmoType.NonSelected)] + static void OnDrawGizmos(Volume volume, GizmoType gizmoType) + { + if (!volume.enabled || volume.isGlobal || volume.colliders == null) + return; var gizmoColor = VolumesPreferences.volumeGizmoColor; var gizmoColorWhenBlendRegionEnabled = new Color(gizmoColor.r, gizmoColor.g, gizmoColor.b, 0.5f * gizmoColor.a); - s_AdditionalGizmoCallbacks.TryGetValue(scr.GetType(), out var callback); - - // Draw a separate gizmo for each collider - foreach (var collider in scr.colliders) + foreach (var collider in volume.colliders) { if (!collider || !collider.enabled) continue; - float blendDistance = 0f; - if (scr is Volume volume) - blendDistance = volume.blendDistance; - - bool blendDistanceEnabled = blendDistance > 0f; // Reduce gizmo opacity when blend region is enabled because there are two of them + bool blendDistanceEnabled = volume.blendDistance > 0f; Gizmos.color = blendDistanceEnabled && VolumesPreferences.drawSolid ? gizmoColorWhenBlendRegionEnabled : gizmoColor; - // We'll just use scaling as an approximation for volume skin. It's far from being - // correct (and is completely wrong in some cases). Ultimately we'd use a distance - // field or at least a tesselate + push modifier on the collider's mesh to get a - // better approximation, but the current Gizmo system is a bit limited and because - // everything is dynamic in Unity and can be changed at anytime, it's hard to keep - // track of changes in an elegant way (which we'd need to implement a nice cache - // system for generated volume meshes). - switch (collider) { - case BoxCollider c: DrawBoxCollider(c); break; - case SphereCollider c: DrawSphereCollider(c); break; - case MeshCollider c: DrawMeshCollider(c); break; - default: - // Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and - // other m_Colliders... + case BoxCollider c: + DrawBoxCollider(c.transform, c.center, c.size); + if (blendDistanceEnabled) + DrawBlendDistanceBox(c, volume.blendDistance); + break; + case SphereCollider c: + DrawSphereCollider(c.transform, c.center, c.radius); + if (blendDistanceEnabled) + DrawBlendDistanceSphere(c, volume.blendDistance); + break; + case MeshCollider c: + DrawMeshCollider(c.transform, c.sharedMesh); break; - } - - void DrawBoxCollider(BoxCollider boxCollider) - { - if (VolumesPreferences.drawWireFrame) - Gizmos.DrawWireCube(boxCollider.center, boxCollider.size); - if (VolumesPreferences.drawSolid) - Gizmos.DrawCube(boxCollider.center, boxCollider.size); - - if (blendDistanceEnabled) - DrawBlendDistanceBox(boxCollider, blendDistance); - - callback?.OnBoxColliderDraw(scr, boxCollider); - } - - void DrawSphereCollider(SphereCollider c) - { - Matrix4x4 oldMatrix = Gizmos.matrix; - // For sphere the only scale that is used is the transform.x - Gizmos.matrix = Matrix4x4.TRS(monoBehaviour.transform.position, monoBehaviour.transform.rotation, - Vector3.one * lossyScale.x); - - if (VolumesPreferences.drawWireFrame) - Gizmos.DrawWireSphere(c.center, c.radius); - if (VolumesPreferences.drawSolid) - Gizmos.DrawSphere(c.center, c.radius); - - if (blendDistanceEnabled) - DrawBlendDistanceSphere(c, blendDistance); - - callback?.OnSphereColliderDraw(scr, c); - - Gizmos.matrix = oldMatrix; - } - - void DrawMeshCollider(MeshCollider c) - { - // Only convex mesh m_Colliders are allowed - if (!c.convex) - c.convex = true; - - if (VolumesPreferences.drawWireFrame) - Gizmos.DrawWireMesh(c.sharedMesh); - if (VolumesPreferences.drawSolid) - // Mesh pivot should be centered or this won't work - Gizmos.DrawMesh(c.sharedMesh); - - callback?.OnMeshColliderDraw(scr, c); } } } @@ -155,5 +128,6 @@ static void DrawBlendDistanceSphere(SphereCollider c, float blendDistance) if (VolumesPreferences.drawSolid) Gizmos.DrawSphere(c.center, blendSphereSize); } +#endif } } diff --git a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeProfileFactory.cs b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeProfileFactory.cs index 9c64879813f..293f7676163 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeProfileFactory.cs +++ b/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeProfileFactory.cs @@ -19,7 +19,7 @@ static void CreateVolumeProfile() 0, ScriptableObject.CreateInstance(), "New Volume Profile.asset", - null, + CoreUtils.GetIconForType(), null ); } @@ -40,7 +40,7 @@ public static void CreateVolumeProfileWithCallback(string fullPath, Action(), null); } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Debugging/VolumeDebugSettings.cs b/Packages/com.unity.render-pipelines.core/Runtime/Debugging/VolumeDebugSettings.cs index 4522aa65810..220e835fd25 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Debugging/VolumeDebugSettings.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Debugging/VolumeDebugSettings.cs @@ -152,6 +152,7 @@ float ComputeWeight(Volume volume, Vector3 triggerPos) float weight = Mathf.Clamp01(volume.weight); if (!volume.isGlobal) { +#if ENABLE_PHYSICS_MODULE var colliders = volume.colliders; // Find closest distance to volume, 0 means it's inside it @@ -172,6 +173,7 @@ float ComputeWeight(Volume volume, Vector3 triggerPos) weight = 0f; else if (blendDistSqr > 0f) weight *= 1f - (closestDistanceSqr / blendDistSqr); +#endif } return weight; } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Deprecated.cs b/Packages/com.unity.render-pipelines.core/Runtime/Deprecated.cs index 44e805368f3..f7950ad1517 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Deprecated.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Deprecated.cs @@ -201,4 +201,44 @@ public override void SetValue(uint value) } } } + + /// + /// Defines the basic structure for a Volume, providing the necessary properties for determining + /// whether the volume should be applied globally to the scene or to specific colliders. + /// + /// + /// This interface serves as a contract for systems that implement volume logic, enabling + /// reusable code for volume-based behaviors such as rendering effects, post-processing, or scene-specific logic. + /// The interface is commonly implemented by components that define volumes in a scene, + /// allowing for flexibility in determining how the volume interacts with the scene. A volume can either be global + /// (affecting the entire scene) or local (restricted to specific colliders). + /// This interface is also helpful for drawing gizmos in the scene view, as it allows for visual representation + /// of volumes in the editor based on their settings. + /// + [Obsolete("IVolume is no longer used. #from(6000.4)", false)] + public interface IVolume + { + /// + /// Gets or sets a value indicating whether the volume applies to the entire scene. + /// If true, the volume is global and affects all objects within the scene. + /// If false, the volume is local and only affects the objects within the specified colliders. + /// + /// + /// When set to true, the volume's effects will be applied universally across the scene, + /// without considering individual colliders. When false, the volume will interact only with + /// the objects inside the colliders defined in . + /// + bool isGlobal { get; set; } + +#if ENABLE_PHYSICS_MODULE + /// + /// A list of colliders that define the area of influence of the volume when is set to false. + /// + /// + /// This property holds the colliders that restrict the volume's effects to specific areas of the scene. + /// It is only relevant when is false, and defines the boundaries of where the volume is applied. + /// + List colliders { get; } +#endif + } } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/GPUDriven/Components/DisallowGPUDrivenRendering.cs b/Packages/com.unity.render-pipelines.core/Runtime/GPUDriven/Components/DisallowGPUDrivenRendering.cs index 8c0b3d9abe3..af9a92dd03d 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/GPUDriven/Components/DisallowGPUDrivenRendering.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/GPUDriven/Components/DisallowGPUDrivenRendering.cs @@ -62,7 +62,8 @@ private static void AllowGPUDrivenRenderingRecursively(Transform transform, bool private void OnValidate() { OnDisable(); - OnEnable(); + if (enabled) + OnEnable(); } } } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeReferenceVolume.cs b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeReferenceVolume.cs index 48e07b5e055..faef35fbfc0 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeReferenceVolume.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeReferenceVolume.cs @@ -1059,7 +1059,6 @@ public void Initialize(in ProbeVolumeSystemParameters parameters) #if UNITY_EDITOR UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += ProbeVolumeBakingSet.OnSceneSaving; - ProbeVolumeBakingSet.SyncBakingSets(); #endif m_EnabledBySRP = true; diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs index 7bb2397b40a..7394f812e16 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs @@ -24,6 +24,9 @@ internal void UpgradeFromTo(ProbeVolumeBakingProcessSettings.SettingsVersion fro [System.Serializable] internal struct VirtualOffsetSettings { + // Duplicated Physics.DefaultRaycastLayers because that dependency is removed from render-pipelines.core. Kept in sync with a unit test. + internal const int kPhysicsDefaultRaycastLayers = ~(1 << 2); + public bool useVirtualOffset; [Range(0f, 0.95f)] public float validityThreshold; [Range(0f, 1f)] public float outOfGeoOffset; @@ -45,7 +48,7 @@ internal void UpgradeFromTo(ProbeVolumeBakingProcessSettings.SettingsVersion fro if (from < ProbeVolumeBakingProcessSettings.SettingsVersion.ThreadedVirtualOffset && to >= ProbeVolumeBakingProcessSettings.SettingsVersion.ThreadedVirtualOffset) { rayOriginBias = -0.001f; - collisionMask = Physics.DefaultRaycastLayers; + collisionMask = kPhysicsDefaultRaycastLayers; } } } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingSet.Editor.cs b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingSet.Editor.cs index 249a1ea2ba7..bf777c2c424 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingSet.Editor.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingSet.Editor.cs @@ -20,9 +20,24 @@ internal class SceneBakeData public Bounds bounds = new(); } + internal class SceneToBakingSet + { + static Dictionary sceneToBakingSet = null; + + internal static Dictionary Instance + { + get + { + if (sceneToBakingSet == null) + sceneToBakingSet = ProbeVolumeBakingSet.SyncBakingSets(); + + return sceneToBakingSet; + } + } + } + [SerializeField] SerializedDictionary m_SceneBakeData = new(); - internal static Dictionary sceneToBakingSet = new Dictionary(); /// /// Tries to add a scene to the baking set. @@ -42,7 +57,8 @@ internal void AddScene(string guid, SceneBakeData bakeData = null) { m_SceneGUIDs.Add(guid); m_SceneBakeData.Add(guid, bakeData != null ? bakeData : new SceneBakeData()); - sceneToBakingSet[guid] = new ProbeVolumeBakingSetWeakReference(this); + + SceneToBakingSet.Instance[guid] = new ProbeVolumeBakingSetWeakReference(this); EditorUtility.SetDirty(this); } @@ -55,7 +71,8 @@ public void RemoveScene(string guid) { m_SceneGUIDs.Remove(guid); m_SceneBakeData.Remove(guid); - sceneToBakingSet.Remove(guid); + + SceneToBakingSet.Instance.Remove(guid); EditorUtility.SetDirty(this); } @@ -64,8 +81,10 @@ internal void SetScene(string guid, int index, SceneBakeData bakeData = null) { var previousSceneGUID = m_SceneGUIDs[index]; m_SceneGUIDs[index] = guid; - sceneToBakingSet.Remove(previousSceneGUID); - sceneToBakingSet[guid] = new ProbeVolumeBakingSetWeakReference(this); + + SceneToBakingSet.Instance.Remove(previousSceneGUID); + SceneToBakingSet.Instance[guid] = new ProbeVolumeBakingSetWeakReference(this); + m_SceneBakeData.Add(guid, bakeData != null ? bakeData : new SceneBakeData()); EditorUtility.SetDirty(this); @@ -289,9 +308,9 @@ public string RenameScenario(string scenario, string newName) return newName; } - internal static void SyncBakingSets() + internal static Dictionary SyncBakingSets() { - sceneToBakingSet = new Dictionary(); + Dictionary sceneToBakingSet = new Dictionary(); var setGUIDs = AssetDatabase.FindAssets("t:" + nameof(ProbeVolumeBakingSet)); @@ -316,9 +335,11 @@ internal static void SyncBakingSets() reference.Unload(); } } + + return sceneToBakingSet; } - internal static ProbeVolumeBakingSet GetBakingSetForScene(string sceneGUID) => sceneToBakingSet.GetValueOrDefault(sceneGUID, null)?.Get(); + internal static ProbeVolumeBakingSet GetBakingSetForScene(string sceneGUID) { return SceneToBakingSet.Instance.GetValueOrDefault(sceneGUID, null)?.Get(); } internal static ProbeVolumeBakingSet GetBakingSetForScene(Scene scene) => GetBakingSetForScene(scene.GetGUID()); internal void SetDefaults() diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing.meta new file mode 100644 index 00000000000..88901cf107d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 99457bdfc44f5084e98a01808d1d00ca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/.buginfo b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/.buginfo new file mode 100644 index 00000000000..484b54b848d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/.buginfo @@ -0,0 +1 @@ +area: Lighting diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs new file mode 100644 index 00000000000..029aa755ad2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal static class AdaptiveSamplingHelpers + { + internal const float AdaptiveThreshold_00 = 0.0f; // Accept no error + internal const float AdaptiveThreshold_01 = 0.01f / 2.58f; // Accept 1% error using 99% confidence interval + internal const float AdaptiveThreshold_05 = 0.05f / 1.96f; // Accept 5% error using 95% confidence interval + internal const float AdaptiveThreshold_10 = 0.10f / 1.645f; // Accept 10% error using 90% confidence interval + internal const float AdaptiveThreshold_20 = 0.20f / 1.285f; // Accept 20% error using 80% confidence interval + internal const float AdaptiveThreshold_30 = 0.30f / 1.035f; // Accept 30% error using 70% confidence interval + internal const float AdaptiveThreshold_40 = 0.40f / 0.845f; // Accept 40% error using 60% confidence interval + internal const float AdaptiveThreshold_50 = 0.50f / 0.675f; // Accept 50% error using 50% confidence interval + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs.meta new file mode 100644 index 00000000000..913acbe410d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AdaptiveSamplingHelpers.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2555d7f783a93094cb7bd2e7f54feca1 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs new file mode 100644 index 00000000000..46036e6eeb3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.Rendering.GIPreview.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.PathTracing.Editor.Tests")] +[assembly: InternalsVisibleTo("Unity.PathTracing.Runtime.Tests")] +[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.HighDefinition.Runtime")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.Universal.Runtime")] +[assembly: InternalsVisibleTo("Unity.PathTracing.Editor")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] +[assembly: InternalsVisibleTo("Assembly-CSharp-testable")] diff --git a/Packages/com.unity.render-pipelines.universal/Editor/Converter/ConversionIndexers.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs.meta similarity index 83% rename from Packages/com.unity.render-pipelines.universal/Editor/Converter/ConversionIndexers.cs.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs.meta index c1cdbb66abe..bd695bb40d6 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/Converter/ConversionIndexers.cs.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/AssemblyInfo.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 192e94d6ebdfb96438e2d027c77f9519 +guid: d2dd2e13c500b9944a6ad6cf50cbbe8d MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs new file mode 100644 index 00000000000..437962726de --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs @@ -0,0 +1,289 @@ +using System; +using Unity.Mathematics; +using UnityEngine.Experimental.Rendering; +using UnityEngine.PathTracing.Integration; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; +using UnityEngine.Rendering.Sampling; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityEngine.PathTracing.Lightmapping +{ + using InstanceHandle = Handle; + using MaterialHandle = Handle; + + internal struct LodInstanceBuildData + { + public int LodMask; + public Mesh Mesh; + public MaterialHandle[] Materials; + public uint[] Masks; + public Matrix4x4 LocalToWorldMatrix; + public Bounds Bounds; + public bool IsStatic; + public RenderedGameObjectsFilter Filter; + } + + internal struct LodIdentifier + { + public LodIdentifier(Int32 lodGroup, byte lodMask, Int32 lodContributorLevel) + { + this.LodGroup = lodGroup; + this.LodMask = lodMask; + this.LodContributorLevel = lodContributorLevel; + } + + public Int32 LodGroup; + public byte LodMask; + Int32 LodContributorLevel; + + public override int GetHashCode() => HashCode.Combine(LodGroup, LodMask); + public override bool Equals(object obj) => obj is LodIdentifier other && other.LodGroup == LodGroup && other.LodMask == LodMask; + public static readonly LodIdentifier Invalid = new LodIdentifier(-1, 0, -1); + public bool IsValid() => LodGroup != -1; + public bool IsContributor() + { + if (LodContributorLevel == -1) return false; + return (LodMask & (1 << LodContributorLevel)) != 0; + } + public byte MinLodLevelMask() { return (byte)(LodMask & -LodMask); } + } + + internal struct ContributorLodInfo + { + public InstanceHandle InstanceHandle; + public uint[] Masks; + public int LodMask; + } + + internal struct FatInstance + { + public BoundingSphere BoundingSphere; + public Mesh Mesh; + public Vector2 UVBoundsSize; + public Vector2 UVBoundsOffset; + public MaterialHandle[] Materials; + public uint[] SubMeshMasks; + public Matrix4x4 LocalToWorldMatrix; + public Bounds Bounds; + public bool IsStatic; + public LodIdentifier LodIdentifier; + public bool ReceiveShadows; + public RenderedGameObjectsFilter Filter; + public uint RenderingObjectLayer; + public bool EnableEmissiveSampling; + } + + internal struct BakeInstance + { + public Mesh Mesh; + public Vector4 NormalizedOccupiedST; // Transforms coordinates in [0; 1] range into the occupied rectangle in the lightmap atlas, also in [0; 1] range. + public Vector4 SourceLightmapST; + public Vector2Int TexelSize; // Instance size in the lightmap atlas, in pixels. + public Vector2Int TexelOffset; // Instance offset in the lightmap atlas, in pixels. + public Matrix4x4 LocalToWorldMatrix; + public Matrix4x4 LocalToWorldMatrixNormals; + public bool ReceiveShadows; + public LodIdentifier LodIdentifier; + public uint InstanceIndex; // Index of the instance in BakeInput + + private static float4x4 NormalMatrix(float4x4 m) + { + float3x3 t = new float3x3(m); + return new float4x4(math.inverse(math.transpose(t)), new float3(0.0)); + } + + public BoundingSphere GetBoundingSphere() + { + var boundingSphere = new BoundingSphere(); + boundingSphere.position = LocalToWorldMatrix.MultiplyPoint(Mesh.bounds.center); + boundingSphere.radius = (LocalToWorldMatrix.MultiplyPoint(Mesh.bounds.extents) - boundingSphere.position).magnitude; + return boundingSphere; + } + + public void Build(Mesh mesh, Vector4 normalizedOccupiedST, Vector4 sourceLightmapST, Vector2Int texelSize, Vector2Int texelOffset, Matrix4x4 localToWorldMatrix, bool receiveShadows, LodIdentifier lodIdentifier, uint instanceIndex) + { + Mesh = mesh; + NormalizedOccupiedST = normalizedOccupiedST; + SourceLightmapST = sourceLightmapST; + TexelSize = texelSize; + TexelOffset = texelOffset; + LocalToWorldMatrix = localToWorldMatrix; + ReceiveShadows = receiveShadows; + LocalToWorldMatrixNormals = NormalMatrix(this.LocalToWorldMatrix); + LodIdentifier = lodIdentifier; + InstanceIndex = instanceIndex; + } + } + + internal class LightmapDesc + { + public uint Resolution; + public float PushOff; + public BakeInstance[] BakeInstances; + } + + internal enum IntegratedOutputType + { + Direct, + Indirect, + AO, + Validity, + DirectionalityDirect, + DirectionalityIndirect, + ShadowMask + } + + internal class LightmapIntegratorContext : IDisposable + { + internal UVFallbackBufferBuilder UVFallbackBufferBuilder; + internal LightmapDirectIntegrator LightmapDirectIntegrator; + internal LightmapIndirectIntegrator LightmapIndirectIntegrator; + internal LightmapAOIntegrator LightmapAOIntegrator; + internal LightmapValidityIntegrator LightmapValidityIntegrator; + internal LightmapOccupancyIntegrator LightmapOccupancyIntegrator; + internal LightmapShadowMaskIntegrator LightmapShadowMaskIntegrator; + internal GBufferDebug GBufferDebugShader; + internal IRayTracingShader GBufferShader; + internal ComputeShader ExpansionShaders; + internal SamplingResources SamplingResources; + private RTHandle _emptyExposureTexture; + internal GraphicsBuffer ClearDispatchBuffer; + internal GraphicsBuffer CopyDispatchBuffer; + internal GraphicsBuffer ReduceDispatchBuffer; + internal GraphicsBuffer CompactedGBufferLength; + internal int CompactGBufferKernel; + internal int PopulateAccumulationDispatchKernel; + internal int PopulateClearDispatchKernel; + internal int PopulateCopyDispatchKernel; + internal int PopulateReduceDispatchKernel; + internal int ClearBufferKernel; + internal int ReductionKernel; + internal int CopyToLightmapKernel; + + public void Dispose() + { + UVFallbackBufferBuilder?.Dispose(); + UVFallbackBufferBuilder = null; + LightmapDirectIntegrator?.Dispose(); + LightmapDirectIntegrator = null; + LightmapIndirectIntegrator?.Dispose(); + LightmapIndirectIntegrator = null; + LightmapAOIntegrator?.Dispose(); + LightmapAOIntegrator = null; + LightmapValidityIntegrator?.Dispose(); + LightmapValidityIntegrator = null; + LightmapOccupancyIntegrator = null; + LightmapShadowMaskIntegrator?.Dispose(); + LightmapShadowMaskIntegrator = null; + GBufferDebugShader?.Dispose(); + GBufferDebugShader = null; + _emptyExposureTexture?.Release(); + _emptyExposureTexture = null; + + ClearDispatchBuffer?.Dispose(); + CopyDispatchBuffer?.Dispose(); + ReduceDispatchBuffer?.Dispose(); + CompactedGBufferLength?.Dispose(); + } + + internal void Initialize(SamplingResources samplingResources, LightmapResourceLibrary lightmapResourceLib, bool countNEERayAsPathSegment) + { + SamplingResources = samplingResources; + _emptyExposureTexture = RTHandles.Alloc(1, 1, enableRandomWrite: true, name: "Empty EV100 Exposure", colorFormat: GraphicsFormat.R8G8B8A8_UNorm); + + UVFallbackBufferBuilder = new UVFallbackBufferBuilder(); + UVFallbackBufferBuilder.Prepare(lightmapResourceLib.UVFallbackBufferGenerationMaterial); + LightmapDirectIntegrator = new LightmapDirectIntegrator(); + LightmapDirectIntegrator.Prepare(lightmapResourceLib.DirectAccumulationShader, lightmapResourceLib.NormalizationShader, lightmapResourceLib.ExpansionHelpers, SamplingResources, _emptyExposureTexture); + LightmapIndirectIntegrator = new LightmapIndirectIntegrator(countNEERayAsPathSegment); + LightmapIndirectIntegrator.Prepare(lightmapResourceLib.IndirectAccumulationShader, lightmapResourceLib.NormalizationShader, lightmapResourceLib.ExpansionHelpers, SamplingResources, _emptyExposureTexture); + LightmapAOIntegrator = new LightmapAOIntegrator(); + LightmapAOIntegrator.Prepare(lightmapResourceLib.AOAccumulationShader, lightmapResourceLib.NormalizationShader, lightmapResourceLib.ExpansionHelpers, SamplingResources, _emptyExposureTexture); + LightmapValidityIntegrator = new LightmapValidityIntegrator(); + LightmapValidityIntegrator.Prepare(lightmapResourceLib.ValidityAccumulationShader, lightmapResourceLib.NormalizationShader, lightmapResourceLib.ExpansionHelpers, SamplingResources, _emptyExposureTexture); + LightmapOccupancyIntegrator = new LightmapOccupancyIntegrator(); + LightmapOccupancyIntegrator.Prepare(lightmapResourceLib.OccupancyShader); + LightmapShadowMaskIntegrator = new LightmapShadowMaskIntegrator(); + LightmapShadowMaskIntegrator.Prepare(lightmapResourceLib.ShadowMaskAccumulationShader, lightmapResourceLib.NormalizationShader, lightmapResourceLib.ExpansionHelpers, SamplingResources, _emptyExposureTexture); + GBufferDebugShader = new GBufferDebug(); + GBufferDebugShader.Prepare(lightmapResourceLib.GBufferDebugShader, lightmapResourceLib.ExpansionHelpers); + GBufferShader = lightmapResourceLib.GBufferShader; + ExpansionShaders = lightmapResourceLib.ExpansionHelpers; + + CompactGBufferKernel = ExpansionShaders.FindKernel("CompactGBuffer"); + PopulateAccumulationDispatchKernel = ExpansionShaders.FindKernel("PopulateAccumulationDispatch"); + PopulateClearDispatchKernel = ExpansionShaders.FindKernel("PopulateClearDispatch"); + PopulateCopyDispatchKernel = ExpansionShaders.FindKernel("PopulateCopyDispatch"); + PopulateReduceDispatchKernel = ExpansionShaders.FindKernel("PopulateReduceDispatch"); + ClearBufferKernel = ExpansionShaders.FindKernel("ClearFloat4Buffer"); + ReductionKernel = ExpansionShaders.FindKernel("BinaryGroupSumLeft"); + CopyToLightmapKernel = ExpansionShaders.FindKernel("AdditivelyCopyCompactedTo2D"); + + ClearDispatchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.CopyDestination, 3, sizeof(uint)); + CopyDispatchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.CopyDestination, 3, sizeof(uint)); + ReduceDispatchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.CopyDestination, 3, sizeof(uint)); + CompactedGBufferLength = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, 1, sizeof(uint)); + } + } + + internal class LightmapResourceLibrary + { + internal IRayTracingShader GBufferShader; + internal ComputeShader NormalizationShader; + internal IRayTracingShader DirectAccumulationShader; + internal IRayTracingShader AOAccumulationShader; + internal IRayTracingShader ValidityAccumulationShader; + internal IRayTracingShader IndirectAccumulationShader; + internal IRayTracingShader ShadowMaskAccumulationShader; + internal IRayTracingShader NormalAccumulationShader; + internal IRayTracingShader GBufferDebugShader; + internal Material UVFallbackBufferGenerationMaterial; + internal ComputeShader OccupancyShader; + internal LightmapIntegrationHelpers.ComputeHelpers ComputeHelpers; + internal ComputeShader BoxFilterShader; + internal ComputeShader SelectGraphicsBufferShader; + internal ComputeShader CopyTextureAdditiveShader; + internal ComputeShader ExpansionHelpers; + internal Shader SoftwareChartRasterizationShader; + internal Shader HardwareChartRasterizationShader; + +#if UNITY_EDITOR + public void Load(RayTracingContext context) + { + const string packageFolder = "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/"; + + GBufferShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapGBufferIntegration.urtshader"); + + NormalizationShader = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/ResolveAccumulation.compute"); + DirectAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapDirectIntegration.urtshader"); + AOAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapAOIntegration.urtshader"); + ValidityAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapValidityIntegration.urtshader"); + IndirectAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapIndirectIntegration.urtshader"); + ShadowMaskAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapShadowMaskIntegration.urtshader"); + GBufferDebugShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapGBufferDebug.urtshader"); + + NormalAccumulationShader = context.LoadRayTracingShader(packageFolder + "Shaders/LightmapNormalIntegration.urtshader"); + + UVFallbackBufferGenerationMaterial = new Material(AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/Lightmapping/UVFallbackBufferGeneration.shader")); + + OccupancyShader = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/LightmapOccupancy.compute"); + + ExpansionHelpers = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/ExpansionHelpers.compute"); + + ComputeHelpers = new LightmapIntegrationHelpers.ComputeHelpers(); + ComputeHelpers.Load(); + + ChartRasterizer.LoadShaders(out SoftwareChartRasterizationShader, out HardwareChartRasterizationShader); + + BoxFilterShader = ComputeHelpers.ComputeHelperShader; + SelectGraphicsBufferShader = ComputeHelpers.ComputeHelperShader; + CopyTextureAdditiveShader = ComputeHelpers.ComputeHelperShader; + } +#endif + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs.meta new file mode 100644 index 00000000000..f7bf46cac39 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e53fede703e0349479a0fba60658fa50 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs new file mode 100644 index 00000000000..a72ab26ebce --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs @@ -0,0 +1,430 @@ +using System; +using Unity.Mathematics; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal static class BakeLightmapDriver + { + public class LightmapBakeState + { + public uint SampleIndex; + public UInt64 TexelIndex; + + public void Init() + { + SampleIndex = 0; + TexelIndex = 0; + } + + public void Tick(uint passSampleCount, uint totalSampleCount, UInt64 chunkTexelCount, UInt64 totalTexelCount, out bool instanceIsDone, out bool chunkIsDone) + { + instanceIsDone = false; + chunkIsDone = false; + + SampleIndex += passSampleCount; + Debug.Assert(SampleIndex <= totalSampleCount); + if (SampleIndex == totalSampleCount) + { + // a chunk is done since we have reached `totalSampleCount` + chunkIsDone = true; + TexelIndex += chunkTexelCount; + SampleIndex = 0; + } + Debug.Assert(TexelIndex <= totalTexelCount); + if (TexelIndex == totalTexelCount) + { + // an instance is done since we have reached `totalTexelCount` + instanceIsDone = true; + } + } + } + + public struct IntegrationSettings + { + public RayTracingBackend Backend; + public uint MaxDispatchesPerFlush; // how many dispatches to do before flushing the GPU + public bool DebugDispatches; + + public static readonly IntegrationSettings Default = new IntegrationSettings + { + Backend = RayTracingBackend.Compute, + MaxDispatchesPerFlush = 1, + DebugDispatches = false + }; + } + + public class LightmapBakeSettings + { + public uint AOSampleCount = 32; + public uint DirectSampleCount = 32; + public uint IndirectSampleCount = 512; + public uint ValiditySampleCount = 512; + + public AntiAliasingType AOAntiAliasingType = AntiAliasingType.Stochastic; + public AntiAliasingType DirectAntiAliasingType = AntiAliasingType.SuperSampling; + public AntiAliasingType IndirectAntiAliasingType = AntiAliasingType.Stochastic; + public AntiAliasingType ValidityAntiAliasingType = AntiAliasingType.Stochastic; + + public uint BounceCount = 4; + public uint DirectLightingEvaluationCount = 4; + public uint IndirectLightingEvaluationCount = 1; + public float AOMaxDistance = 1.0f; + public float PushOff = 0.00001f; + public UInt64 ExpandedBufferSize = 262144; + public uint GetSampleCount(IntegratedOutputType integratedOutputType) + { + switch (integratedOutputType) + { + case IntegratedOutputType.AO: return AOSampleCount; + case IntegratedOutputType.Direct: return DirectSampleCount; + case IntegratedOutputType.DirectionalityDirect: return DirectSampleCount; + case IntegratedOutputType.Indirect: return IndirectSampleCount; + case IntegratedOutputType.DirectionalityIndirect: return IndirectSampleCount; + case IntegratedOutputType.Validity: return ValiditySampleCount; + case IntegratedOutputType.ShadowMask: return DirectSampleCount; + default: + Debug.Assert(false, "Unexpected case."); + return 0; + } + } + + public AntiAliasingType GetAntiAliasingType(IntegratedOutputType integratedOutputType) + { + switch (integratedOutputType) + { + case IntegratedOutputType.AO: return AOAntiAliasingType; + case IntegratedOutputType.Direct: return DirectAntiAliasingType; + case IntegratedOutputType.DirectionalityDirect: return DirectAntiAliasingType; + case IntegratedOutputType.Indirect: return IndirectAntiAliasingType; + case IntegratedOutputType.DirectionalityIndirect: return IndirectAntiAliasingType; + case IntegratedOutputType.Validity: return ValidityAntiAliasingType; + case IntegratedOutputType.ShadowMask: return DirectAntiAliasingType; + default: + Debug.Assert(false, "Unexpected case."); + return 0; + } + } + } + + static bool IsNewChunkStarted( + uint maxChunkSize, + uint instanceWidth, + uint instanceHeight, + uint currentChunkTexelOffset, // current starting texel index for the chunk, linear offset into instanceWidth*instanceHeight + uint currentSampleIndex, // current sample index for the chunk + uint maxSamplesPerTexel, // total sample count per texel + out uint chunkSize, // number of texels to process in a single pass + out uint expandedSampleWidth, // number of expanded samples per texel, power of two, this might exceed the required sample count + out uint passSampleCount, // the actual number of samples to take, this might be smaller than the expanded sample width + out uint2 chunkOffset // the chunk offset in 2D + ) + { + // this function should only be called when there is work to do + Debug.Assert(currentSampleIndex < maxSamplesPerTexel); + Debug.Assert(maxChunkSize > 0); + Debug.Assert(instanceWidth > 0); + Debug.Assert(instanceHeight > 0); + Debug.Assert(currentChunkTexelOffset < instanceWidth * instanceHeight); + chunkOffset = new uint2((uint)(currentChunkTexelOffset % (UInt64)instanceWidth), (uint)(currentChunkTexelOffset / (UInt64)instanceWidth)); + Debug.Assert(chunkOffset.x < instanceWidth); + Debug.Assert(chunkOffset.y < instanceHeight); + uint remainingTexels = (uint)instanceWidth - chunkOffset.x + ((uint)(instanceHeight - 1) - chunkOffset.y) * (uint)instanceWidth; + Debug.Assert(remainingTexels > 0); + uint remainingSampleCount = math.max(0, maxSamplesPerTexel - currentSampleIndex); + Debug.Assert(remainingSampleCount > 0); + + // Choose the size of chunk to take + chunkSize = math.min(remainingTexels, maxChunkSize); // Take as many *texels* as possible in a single pass - this is done to reduce the number of dispatches for compaction, reduction and `copy to lightmap` + Debug.Assert(chunkSize <= maxChunkSize); + + // Calculate the maximum number of samples that can be taken in a single pass + uint maxSamplesPerChunk = math.min(maxChunkSize / chunkSize, maxSamplesPerTexel); // The maximum number of samples we can take for the current chunk + Debug.Assert(chunkSize * maxSamplesPerChunk <= maxChunkSize); + + // Sample count expansion needs to be a power of 2 - calculate the expansion width + expandedSampleWidth = math.ceilpow2(maxSamplesPerChunk); + if (expandedSampleWidth > maxSamplesPerChunk) expandedSampleWidth /= 2; + Debug.Assert(expandedSampleWidth >= 1); + Debug.Assert(expandedSampleWidth * chunkSize <= maxChunkSize); + + // Calculate how many samples we can take in this pass + passSampleCount = math.min(maxSamplesPerChunk, math.min(remainingSampleCount, expandedSampleWidth)); + Debug.Assert(passSampleCount > 0); + Debug.Assert(passSampleCount <= expandedSampleWidth); + Debug.Assert(passSampleCount + currentSampleIndex <= maxSamplesPerTexel); + return currentSampleIndex == 0; // When the sample count has rolled back to zero a new chunk has started + } + + internal static uint AccumulateLightmapInstance( + LightmapBakeState bakeState, + BakeInstance instance, + LightmapBakeSettings lightmapBakeSettings, + IntegratedOutputType integratedOutputType, + LightmappingContext lightmappingContext, + UVAccelerationStructure uvAS, + UVFallbackBuffer uvFallbackBuffer, + bool doDirectionality, + out uint chunkSize, + out bool instanceIsDone) + { + CommandBuffer cmd = lightmappingContext.GetCommandBuffer(); + GraphicsBuffer traceScratchBuffer = lightmappingContext.TraceScratchBuffer; + var ctx = lightmappingContext.IntegratorContext; + var expansionShaders = ctx.ExpansionShaders; + bool doDirectional = doDirectionality || integratedOutputType == IntegratedOutputType.ShadowMask; // Shadowmask uses the directional buffer to store the sample count - so also process that + Vector2Int instanceTexelOffset = instance.TexelOffset; + + { + var maxSampleCountPerTexel = lightmapBakeSettings.GetSampleCount(integratedOutputType); + var instanceWidth = instance.TexelSize.x; + var instanceHeight = instance.TexelSize.y; + var instanceTexelCount = (UInt64)instanceWidth * (UInt64)instanceHeight; + var sampleOffset = bakeState.SampleIndex; + var maxChunkSize = (uint)lightmappingContext.ExpandedOutput.count; + bool newChunkStarted = IsNewChunkStarted( + maxChunkSize, + (uint)instanceWidth, + (uint)instanceHeight, + (uint)bakeState.TexelIndex, + sampleOffset, + maxSampleCountPerTexel, + out chunkSize, + out uint expandedSampleWidth, + out uint passSampleCount, + out uint2 chunkOffset); + + if (newChunkStarted) + { + // compact the texels + ExpansionHelpers.CompactGBuffer(cmd, expansionShaders, ctx.CompactGBufferKernel, (uint)instanceWidth, chunkSize, chunkOffset, uvFallbackBuffer, ctx.CompactedGBufferLength, lightmappingContext.CompactedTexelIndices); + + // clear the expanded output buffer + // Populate the expanded clear indirect dispatch buffer - using the compacted size. + expansionShaders.GetKernelThreadGroupSizes(ctx.ClearBufferKernel, out uint clearThreadGroupSizeX, out uint clearThreadGroupSizeY, out uint clearThreadGroupSizeZ); + Debug.Assert(clearThreadGroupSizeY == 1 && clearThreadGroupSizeZ == 1); + ExpansionHelpers.PopulateClearExpandedOutputIndirectDispatch(cmd, expansionShaders, ctx.PopulateClearDispatchKernel, clearThreadGroupSizeX, expandedSampleWidth, ctx.CompactedGBufferLength, ctx.ClearDispatchBuffer); + // Clear the output buffers. + ExpansionHelpers.ClearExpandedOutput(cmd, expansionShaders, ctx.ClearBufferKernel, lightmappingContext.ExpandedOutput, ctx.ClearDispatchBuffer); + if (doDirectional) + ExpansionHelpers.ClearExpandedOutput(cmd, expansionShaders, ctx.ClearBufferKernel, lightmappingContext.ExpandedDirectional, ctx.ClearDispatchBuffer); + } + + // Work out the super sampling resolution. It's the width of the N x N supersampling kernel. Find the largest perfect square that is less than or equal to the max sample count per texel. + uint superSampleWidth = Math.Max(1, (uint)Math.Sqrt(maxSampleCountPerTexel)); + + // generate the GBuffer + ExpansionHelpers.GenerateGBuffer( + cmd, + lightmappingContext.IntegratorContext.GBufferShader, + lightmappingContext.GBuffer, + traceScratchBuffer, + lightmappingContext.IntegratorContext.SamplingResources, + uvAS, + uvFallbackBuffer, + ctx.CompactedGBufferLength, + lightmappingContext.CompactedTexelIndices, + instanceTexelOffset, + chunkOffset, + chunkSize, + expandedSampleWidth, + passSampleCount, + sampleOffset, + lightmapBakeSettings.GetAntiAliasingType(integratedOutputType), + superSampleWidth + ); + + GraphicsBuffer expandedDirectional = lightmappingContext.ExpandedDirectional; + var instanceGeometryIndex = lightmappingContext.World.PathTracingWorld.GetAccelerationStructure().GeometryPool.GetInstanceGeometryIndex(instance.Mesh); + + bool debugGBuffer = false; + if (debugGBuffer) + { + Debug.Log($"Lightmap resolution: {lightmappingContext.Width} x {lightmappingContext.Height}"); + Debug.Log($"Instance resolution: {instanceWidth} x {instanceHeight}"); + Debug.Log($"Instance offset: {instanceTexelOffset}"); + Debug.Log($"Sample count: {maxSampleCountPerTexel}"); + var occupancy = (double)(passSampleCount * chunkSize) / (double)maxChunkSize * 100.0; + Debug.Log(string.Format(System.Globalization.CultureInfo.InvariantCulture, "Occupancy: {0:F2}%", occupancy)); + // write out the lightmap UV samples + var uvSampleData = ExpansionHelpers.DebugGBuffer(cmd, instance, lightmappingContext, expandedSampleWidth, passSampleCount); + string sampleOutput = new(""); + foreach (var sample in uvSampleData) + sampleOutput += string.Format(System.Globalization.CultureInfo.InvariantCulture, "float2({0}, {1})\n", sample.x, sample.y); + + System.Console.WriteLine(sampleOutput); + } + + // accumulate the lightmap texel + cmd.BeginSample("AccumulateLightmapInstance"); + + switch (integratedOutputType) + { + case IntegratedOutputType.AO: + { + lightmappingContext.IntegratorContext.LightmapAOIntegrator.Accumulate( + cmd, + passSampleCount, + bakeState.SampleIndex, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + instance.TexelSize, + chunkOffset, + lightmappingContext.World.PathTracingWorld, + traceScratchBuffer, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmappingContext.ExpandedOutput, + lightmappingContext.CompactedTexelIndices, + lightmappingContext.IntegratorContext.CompactedGBufferLength, + lightmapBakeSettings.PushOff, + lightmapBakeSettings.AOMaxDistance, + newChunkStarted + ); + break; + } + case IntegratedOutputType.Validity: + { + lightmappingContext.IntegratorContext.LightmapValidityIntegrator.Accumulate( + cmd, + passSampleCount, + bakeState.SampleIndex, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + instance.TexelSize, + chunkOffset, + lightmappingContext.World.PathTracingWorld, + traceScratchBuffer, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmappingContext.ExpandedOutput, + lightmappingContext.CompactedTexelIndices, + lightmappingContext.IntegratorContext.CompactedGBufferLength, + lightmapBakeSettings.PushOff, + newChunkStarted + ); + break; + } + case IntegratedOutputType.Direct: + case IntegratedOutputType.DirectionalityDirect: + { + lightmappingContext.IntegratorContext.LightmapDirectIntegrator.Accumulate( + cmd, + passSampleCount, + bakeState.SampleIndex, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + instance.TexelSize, + chunkOffset, + lightmappingContext.World.PathTracingWorld, + traceScratchBuffer, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmappingContext.ExpandedOutput, + expandedDirectional, + lightmappingContext.CompactedTexelIndices, + lightmappingContext.IntegratorContext.CompactedGBufferLength, + instance.ReceiveShadows, + lightmapBakeSettings.PushOff, + lightmapBakeSettings.DirectLightingEvaluationCount, + newChunkStarted + ); + break; + } + case IntegratedOutputType.Indirect: + case IntegratedOutputType.DirectionalityIndirect: + { + lightmappingContext.IntegratorContext.LightmapIndirectIntegrator.Accumulate( + cmd, + passSampleCount, + bakeState.SampleIndex, + lightmapBakeSettings.BounceCount, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + instance.TexelSize, + chunkOffset, + lightmappingContext.World.PathTracingWorld, + traceScratchBuffer, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmappingContext.ExpandedOutput, + expandedDirectional, + lightmappingContext.CompactedTexelIndices, + lightmappingContext.IntegratorContext.CompactedGBufferLength, + lightmapBakeSettings.PushOff, + lightmapBakeSettings.IndirectLightingEvaluationCount, + newChunkStarted + ); + break; + } + case IntegratedOutputType.ShadowMask: + { + lightmappingContext.IntegratorContext.LightmapShadowMaskIntegrator.Accumulate( + cmd, + passSampleCount, + bakeState.SampleIndex, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + instance.TexelSize, + chunkOffset, + lightmappingContext.World.PathTracingWorld, + traceScratchBuffer, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmappingContext.ExpandedOutput, + expandedDirectional, + lightmappingContext.CompactedTexelIndices, + lightmappingContext.IntegratorContext.CompactedGBufferLength, + instance.ReceiveShadows, + lightmapBakeSettings.PushOff, + lightmapBakeSettings.DirectLightingEvaluationCount, + newChunkStarted + ); + break; + } + } + cmd.EndSample("AccumulateLightmapInstance"); + + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, lightmappingContext.ExpandedOutput, "expandedOutput", LightmapIntegrationHelpers.LogBufferType.Float4); + + // Update the baking state + bakeState.Tick( + passSampleCount, + maxSampleCountPerTexel, + chunkSize, + instanceTexelCount, + out instanceIsDone, + out bool chunkIsDone); + + if (chunkIsDone) + { + int maxExpandedDispatchSize = instanceWidth * instanceHeight * (int)expandedSampleWidth; + // Gather to lightmap -> first reduce to output resolution + // Populate the reduce indirect dispatch buffer - using the compacted size. + expansionShaders.GetKernelThreadGroupSizes(ctx.ReductionKernel, out uint reduceThreadGroupSizeX, out uint reduceThreadGroupSizeY, out uint reduceThreadGroupSizeZ); + Debug.Assert(reduceThreadGroupSizeY == 1 && reduceThreadGroupSizeZ == 1); + ExpansionHelpers.PopulateReduceExpandedOutputIndirectDispatch(cmd, expansionShaders, ctx.PopulateReduceDispatchKernel, reduceThreadGroupSizeX, expandedSampleWidth, ctx.CompactedGBufferLength, ctx.ReduceDispatchBuffer); + ExpansionHelpers.ReduceExpandedOutput(cmd, expansionShaders, ctx.ReductionKernel, lightmappingContext.ExpandedOutput, maxExpandedDispatchSize, expandedSampleWidth, ctx.ReduceDispatchBuffer); + if (doDirectional) + ExpansionHelpers.ReduceExpandedOutput(cmd, expansionShaders, ctx.ReductionKernel, lightmappingContext.ExpandedDirectional, maxExpandedDispatchSize, expandedSampleWidth, ctx.ReduceDispatchBuffer); + + // Populate the copy indirect dispatch buffer - using the compacted size. + expansionShaders.GetKernelThreadGroupSizes(ctx.CopyToLightmapKernel, out uint copyThreadGroupSizeX, out uint copyThreadGroupSizeY, out uint copyThreadGroupSizeZ); + Debug.Assert(copyThreadGroupSizeY == 1 && copyThreadGroupSizeZ == 1); + ExpansionHelpers.PopulateCopyToLightmapIndirectDispatch(cmd, expansionShaders, ctx.PopulateCopyDispatchKernel, copyThreadGroupSizeX, ctx.CompactedGBufferLength, ctx.CopyDispatchBuffer); + ExpansionHelpers.CopyToLightmap(cmd, expansionShaders, ctx.CopyToLightmapKernel, expandedSampleWidth, instanceWidth, instanceTexelOffset, chunkOffset, ctx.CompactedGBufferLength, lightmappingContext.CompactedTexelIndices, lightmappingContext.ExpandedOutput, ctx.CopyDispatchBuffer, lightmappingContext.AccumulatedOutput); + if (doDirectional) + ExpansionHelpers.CopyToLightmap(cmd, expansionShaders, ctx.CopyToLightmapKernel, expandedSampleWidth, instanceWidth, instanceTexelOffset, chunkOffset, ctx.CompactedGBufferLength, lightmappingContext.CompactedTexelIndices, lightmappingContext.ExpandedDirectional, ctx.CopyDispatchBuffer, lightmappingContext.AccumulatedDirectionalOutput); + } + return passSampleCount; + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs.meta new file mode 100644 index 00000000000..d4210f0fa15 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/BakeLightmapDriver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4d348a097ea36d6418bdf3e858a808d0 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core.meta new file mode 100644 index 00000000000..0ef945d9990 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fb8d6a4d744c6343a2307687e61e916 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs new file mode 100644 index 00000000000..0c1b65aca60 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs @@ -0,0 +1,155 @@ + +using System; +using System.Collections.Generic; + +namespace UnityEngine.PathTracing.Core +{ + internal class TextureQuadTree + { + public class TextureNode + { + public TextureNode TopLeft; + public TextureNode TopRight; + public TextureNode BottomLeft; + public TextureNode BottomRight; + + public TextureNode Parent; + + public int PosX; + public int PosY; + public int Size; + } + + private readonly int _size; + private readonly List _leaves; + + public TextureQuadTree(int size) + { + _size = size; + var root = new TextureNode() { PosX = 0, PosY = 0, Size = _size }; + _leaves = new List() { root }; + } + + private void SubdivideNode(TextureNode node) + { + node.TopLeft = new TextureNode() + { + PosX = node.PosX, + PosY = node.PosY, + Size = node.Size / 2, + Parent = node + }; + node.BottomLeft = new TextureNode() + { + PosX = node.PosX, + PosY = node.PosY + node.Size / 2, + Size = node.Size / 2, + Parent = node + }; + node.TopRight = new TextureNode() + { + PosX = node.PosX + node.Size / 2, + PosY = node.PosY, + Size = node.Size / 2, + Parent = node + }; + node.BottomRight = new TextureNode() + { + PosX = node.PosX + node.Size / 2, + PosY = node.PosY + node.Size / 2, + Size = node.Size / 2, + Parent = node + }; + } + + public bool AddTexture(int size, out TextureNode node) + { + int targetSize = Mathf.Min(Mathf.NextPowerOfTwo(size), _size); + + // Search for a node with the right size + int bestSize = int.MaxValue; + TextureNode candidate = null; + + for (int i = _leaves.Count - 1; i >= 0; i--) + { + // If we have a match, return it + int leafSize = _leaves[i].Size; + if (targetSize == leafSize) + { + node = _leaves[i]; + _leaves.RemoveAt(i); + return true; + } + + // Keep track of the best candidate we find + if (targetSize < leafSize && leafSize < bestSize) + { + candidate = _leaves[i]; + bestSize = leafSize; + } + } + + // If we have a candidate, subdivide one child until we are at the right size + if (candidate != null) + { + while (candidate.Size != targetSize) + { + SubdivideNode(candidate); + _leaves.Remove(candidate); + _leaves.Add(candidate.BottomRight); + _leaves.Add(candidate.BottomLeft); + _leaves.Add(candidate.TopRight); + candidate = candidate.TopLeft; + } + node = candidate; + return true; + } + + node = null; + return false; + } + + public void RemoveTexture(TextureNode node) + { + bool ShouldCollapse(TextureNode node) + { + return + (node.TopLeft == null || _leaves.Contains(node.TopLeft)) && + (node.TopRight == null || _leaves.Contains(node.TopRight)) && + (node.BottomLeft == null || _leaves.Contains(node.BottomLeft)) && + (node.BottomRight == null || _leaves.Contains(node.BottomRight)); + } + + // Add the node to the list of leaves + _leaves.Add(node); + + // Recursively collapse the quad tree if the parent node is empty, to avoid fragmentation + TextureNode parent = node.Parent; + while (parent != null && ShouldCollapse(parent)) + { + // Remove the children from the leaves list + if (_leaves.Contains(parent.TopLeft)) _leaves.Remove(parent.TopLeft); + if (_leaves.Contains(parent.TopRight)) _leaves.Remove(parent.TopRight); + if (_leaves.Contains(parent.BottomLeft)) _leaves.Remove(parent.BottomLeft); + if (_leaves.Contains(parent.BottomRight)) _leaves.Remove(parent.BottomRight); + + // Collapse the parent node + parent.TopLeft = null; + parent.TopRight = null; + parent.BottomLeft = null; + parent.BottomRight = null; + + // Add the parent to the leaves list, and check the next parent + _leaves.Add(parent); + parent = parent.Parent; + } + } + + public bool HasSpaceForTexture(int textureSize) + { + return _leaves.Exists(leaf => leaf.Size >= textureSize); + } + + public bool IsFull => _leaves.Count == 0; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs.meta new file mode 100644 index 00000000000..e787693e9d0 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureQuadTree.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b4536f3e86fa534d9779cfdf6d8f12e \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs new file mode 100644 index 00000000000..9cb77d4e32e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs @@ -0,0 +1,212 @@ + +using System; +using System.Collections.Generic; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Core +{ + // Container type that stores a set of arbitrarily sized textures in a 2D texture array, + // where each slice is a quadtree. Supports add/update/remove operations. + internal class TextureSlotAllocator : IDisposable + { + public readonly struct TextureLocation + { + public readonly int AtlasIndex; // Which atlas is the texture in + public readonly TextureQuadTree.TextureNode TextureNode; // Which position in the atlas is the texture + + public TextureLocation(int atlasIndex, TextureQuadTree.TextureNode textureNode) + { + AtlasIndex = atlasIndex; + TextureNode = textureNode; + } + + public static readonly TextureLocation Invalid = new(-1, null); + public bool IsValid => AtlasIndex >= 0 && TextureNode != null; + } + + private readonly int _size; + private readonly GraphicsFormat _format; + private readonly FilterMode _filterMode; + private readonly CommandBuffer _cmd; + private RenderTexture _atlases; + + private readonly HashSet _freeAtlases = new(); + private TextureQuadTree[] _textureQuadTrees = Array.Empty(); + + // Texture sizes stored in each quadtree node don't necessarily match the size of the quadtree node. + // We keep track of the real texture size here, so we can write to the atlas without stretching. + private readonly Dictionary _textureSizes = new(); + + private readonly int _tempTextureID = Shader.PropertyToID("_TempSlotRT"); + + public RenderTexture Texture => _atlases; + + public TextureSlotAllocator(int size, GraphicsFormat format, FilterMode filterMode) + { + _size = size; + _format = format; + _filterMode = filterMode; + _cmd = new CommandBuffer(); + _cmd.name = $"Texture Slot Allocator ({size}, {format})"; + + // Initial allocation, in order to have a texture atlas in place even if we don't add anything to it. + // Note: This is required when binding the textures to ray tracing shaders (compute shaders seem to be OK with null textures) + ResizeAtlas(1); + } + + public TextureLocation AddTexture(Texture texture, Vector2 scale, Vector2 offset) + { + // Find the max dimension + int size = Mathf.Max(texture.width, texture.height); + + // Try to find an appropriately sized atlas in the free list + int atlasIndex = -1; + TextureQuadTree atlas = null; + foreach (int freeIndex in _freeAtlases) + { + if (_textureQuadTrees[freeIndex].HasSpaceForTexture(size)) + { + atlasIndex = freeIndex; + atlas = _textureQuadTrees[atlasIndex]; + break; + } + } + + // If we couldn't find one, create a new atlas + if (atlasIndex == -1 || atlas == null) + { + // Create a new atlas + atlasIndex = _textureQuadTrees.Length; + Array.Resize(ref _textureQuadTrees, atlasIndex + 1); + atlas = new TextureQuadTree(_size); + _textureQuadTrees[atlasIndex] = atlas; + ResizeAtlas(atlasIndex + 1); + + // Add it to the free list + _freeAtlases.Add(atlasIndex); + } + + // Place in atlas + if (!atlas.AddTexture(size, out var textureNode)) + { + return TextureLocation.Invalid; + } + + // We might have filled up the atlas. If so, we remove it from the free list, if it is there. + if (atlas.IsFull) + { + _freeAtlases.Remove(atlasIndex); + } + + // Blit the texture into the atlas + TextureLocation location = new TextureLocation(atlasIndex, textureNode); + UpdateTexture(in location, texture, scale, offset); + return location; + } + + public void UpdateTexture(in TextureLocation location, Texture texture, Vector2 scale, Vector2 offset) + { + TextureQuadTree.TextureNode node = location.TextureNode; + int atlasIndex = location.AtlasIndex; + Debug.Assert(atlasIndex < _atlases.volumeDepth, "The texture atlas is too small."); + + Vector2 textureScale = new Vector2( + node.Size / (float)Mathf.Min(texture.width, node.Size), + node.Size / (float)Mathf.Min(texture.height, node.Size)); + + _cmd.GetTemporaryRT(_tempTextureID, new RenderTextureDescriptor(node.Size, node.Size, _format, 0)); + _cmd.Blit(texture, _tempTextureID, textureScale * scale, offset); + _cmd.CopyTexture(_tempTextureID, 0, 0, 0, 0, node.Size, node.Size, _atlases, atlasIndex, 0, node.PosX, node.PosY); + _cmd.ReleaseTemporaryRT(_tempTextureID); + Graphics.ExecuteCommandBuffer(_cmd); + _cmd.Clear(); + + // Keep track of texture size + _textureSizes[location] = new Vector2Int(texture.width, texture.height); + } + + public void RemoveTexture(in TextureLocation location) + { + Debug.Assert(location.AtlasIndex < _textureQuadTrees.Length); + var atlas = _textureQuadTrees[location.AtlasIndex]; + atlas.RemoveTexture(location.TextureNode); + + // After removal, the atlas can't be full, so make sure it's in the free list + _freeAtlases.Add(location.AtlasIndex); + + // No need to hold on to texture size anymore + _textureSizes.Remove(location); + + // We could choose to shrink the atlas here to save memory, + // but don't do so currently due to the overhead of resizing the atlas. + } + + public Vector2Int GetTextureSize(in TextureLocation location) + { + return new Vector2Int( + Mathf.Min(_textureSizes[location].x, location.TextureNode.Size), + Mathf.Min(_textureSizes[location].y, location.TextureNode.Size)); + } + + public void GetScaleAndOffset(in TextureLocation location, out Vector2 scale, out Vector2 offset) + { + Debug.Assert(location.AtlasIndex < _textureQuadTrees.Length); + + Vector2Int textureSize = GetTextureSize(location); + scale = new Vector2(textureSize.x / (float)_size, textureSize.y / (float)_size); + offset = new Vector2(location.TextureNode.PosX / (float)_size, location.TextureNode.PosY / (float)_size); + } + + private void ResizeAtlas(int sliceCount) + { + // Create new atlas array + var texture = new RenderTexture(new RenderTextureDescriptor(_size, _size) + { + dimension = TextureDimension.Tex2DArray, + depthBufferBits = 0, + volumeDepth = sliceCount, + msaaSamples = 1, + vrUsage = VRTextureUsage.OneEye, + graphicsFormat = _format, + enableRandomWrite = true, + }) + { + name = $"Texture Atlas (Format = {_format})", + hideFlags = HideFlags.DontSaveInEditor, + wrapMode = TextureWrapMode.Clamp, + wrapModeU = TextureWrapMode.Clamp, + wrapModeV = TextureWrapMode.Clamp, + filterMode = _filterMode, + }; + texture.Create(); + + if (_atlases != null) + { + var previousRT = RenderTexture.active; + // Blit old atlases into new one + for (int i = 0; i < Mathf.Min(sliceCount, _atlases.volumeDepth); i++) + { + Graphics.Blit(_atlases, texture, i, i); + } + RenderTexture.active = previousRT; + // Release old atlases + _atlases.Release(); + CoreUtils.Destroy(_atlases); + } + + _atlases = texture; + } + + public void Dispose() + { + _cmd?.Dispose(); + + if (_atlases != null) + { + _atlases.Release(); + CoreUtils.Destroy(_atlases); + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs.meta new file mode 100644 index 00000000000..158cc50f0f3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Core/TextureSlotAllocator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5f1e2bdf0b363ad42b67c77917a4efc9 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment.meta new file mode 100644 index 00000000000..7b0b0854991 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fef47f3c37586ba45b0c35e831041767 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs new file mode 100644 index 00000000000..66d15b76672 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs @@ -0,0 +1,173 @@ +using System; +using Unity.Mathematics; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; + +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace UnityEngine.PathTracing.Core +{ + internal class CubemapRender : IDisposable + { + private Material _material; + + public CubemapRender(Mesh skyboxMesh, Mesh sixSixFaceSkyboxMesh) + { + _skyboxMesh = skyboxMesh; + _sixFaceSkyboxMesh = sixSixFaceSkyboxMesh; + _currentSkyboxHash = 0; + } + + public void Dispose() + { + if (_skyboxTexture != null) + { + _skyboxTexture.Release(); + CoreUtils.Destroy(_skyboxTexture); + } + } + + public void SetMaterial(Material mat) + { + _material = mat; + } + + public Material GetMaterial() => _material; + + Color LightColorInRenderingSpace(Light light) + { + Color cct = light.useColorTemperature ? Mathf.CorrelatedColorTemperatureToRGB(light.colorTemperature) : new Color(1, 1, 1, 1); + Color filter = light.color.linear; + return cct * filter * light.intensity; + } + + public Texture GetCubemap(int cubemapResolution, out int hash) + { + if (!_material) + { + // Note: prefabs have a null skyboxMaterial + hash = 42; + return CoreUtils.blackCubeTexture; + } + + hash = _material.ComputeCRC(); + var light = RenderSettings.sun; + if (light != null) + { + var color = LightColorInRenderingSpace(light); + var dir = -light.GetComponent().forward; + hash ^= HashCode.Combine(color.r, color.g, color.b); + hash ^= HashCode.Combine(dir.x, dir.y, dir.z); + } + + if (hash != _currentSkyboxHash) + { + Render(cubemapResolution); + _currentSkyboxHash = hash; + } + + return _skyboxTexture; + } + + public Texture Render(int cubemapResolution) + { + if (_skyboxTexture == null || _skyboxTexture.width != cubemapResolution) + _skyboxTexture = CreateSkyboxTexture(cubemapResolution); + + using var cmd = new CommandBuffer(); + + // We don't want to render a (procedural) sun in our skybox for path tracing as it's already sampled as direct light + bool isSunDisabled = _material.IsKeywordEnabled("_SUNDISK_NONE"); + if (!isSunDisabled) + _material.EnableKeyword("_SUNDISK_NONE"); + + var light = RenderSettings.sun; + var properties = new MaterialPropertyBlock(); + if (light != null) + { + properties.SetVector(Shader.PropertyToID("_LightColor0"), LightColorInRenderingSpace(light)); + properties.SetVector(Shader.PropertyToID("_WorldSpaceLightPos0"), -light.GetComponent().forward); + } + + for (int faceIndex = 0; faceIndex < 6; ++faceIndex) + { + var viewMatrix = CubemapFaceBases[faceIndex]; + var proj = Matrix4x4.Perspective(90.0f, 1.0f, 0.1f, 10.0f); + if (IsOpenGLGfxDevice()) + proj.SetColumn(1, -proj.GetColumn(1)); // flip Y axis + + cmd.SetViewProjectionMatrices(viewMatrix, proj); + cmd.SetRenderTarget(new RenderTargetIdentifier(_skyboxTexture, 0, (CubemapFace) faceIndex)); + cmd.SetViewport(new Rect(0, 0, _skyboxTexture.width, _skyboxTexture.height)); + cmd.ClearRenderTarget(false, true, new Color(0, 0, 0, 1)); + +#if UNITY_EDITOR + ShaderUtil.SetAsyncCompilation(cmd, false); +#endif + if (_material.passCount == 6) + { + int passIndex = CubeFaceToSkyboxPass[faceIndex]; + cmd.DrawMesh(_sixFaceSkyboxMesh, Matrix4x4.identity, _material, passIndex, passIndex, properties); + } + else + { + cmd.DrawMesh(_skyboxMesh, Matrix4x4.identity, _material, 0, 0, properties); + } +#if UNITY_EDITOR + ShaderUtil.RestoreAsyncCompilation(cmd); +#endif + } + + Graphics.ExecuteCommandBuffer(cmd); + + if (!isSunDisabled) + _material.DisableKeyword("_SUNDISK_NONE"); + + return _skyboxTexture; + } + + private static readonly Matrix4x4[] CubemapFaceBases = new Matrix4x4[6] + { + new(new float4(0, 0, -1, 0), new float4(0, 1, 0, 0), new float4(-1, 0, 0, 0), new float4(0, 0, 0, 1)), + new(new float4(0, 0, 1, 0), new float4(0, 1, 0, 0), new float4(1, 0, 0, 0), new float4(0, 0, 0, 1)), + new(new float4(1, 0, 0, 0), new float4(0, 0, -1, 0), new float4(0, -1, 0, 0), new float4(0, 0, 0, 1)), + new(new float4(1, 0, 0, 0), new float4(0, 0, 1, 0), new float4(0, 1, 0, 0), new float4(0, 0, 0, 1)), + new(new float4(1, 0, 0, 0), new float4(0, 1, 0, 0), new float4(0, 0, -1, 0), new float4(0, 0, 0, 1)), + new(new float4(-1, 0, 0, 0), new float4(0, 1, 0, 0), new float4(0, 0, 1, 0), new float4(0, 0, 0, 1)), + }; + + private static readonly int[] CubeFaceToSkyboxPass = { 2, 3, 4, 5, 0, 1 }; + + private readonly Mesh _skyboxMesh; + private readonly Mesh _sixFaceSkyboxMesh; + private RenderTexture _skyboxTexture; + private int _currentSkyboxHash; + + private RenderTexture CreateSkyboxTexture(int width) + { + var texture = new RenderTexture(new RenderTextureDescriptor(){ + dimension = TextureDimension.Cube, + width = width, + height = width, + depthBufferBits = 0, + volumeDepth = 1, + msaaSamples = 1, + vrUsage = VRTextureUsage.OneEye, + graphicsFormat = GraphicsFormat.R16G16B16A16_SFloat, + enableRandomWrite = true + }); + texture.Create(); + texture.name = "CreateSkyboxTexture"; + return texture; + } + + private bool IsOpenGLGfxDevice() + { + return SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3 || + SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs.meta similarity index 83% rename from Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs.meta index d64458c6d70..7bdad904408 100644 --- a/Packages/com.unity.render-pipelines.core/Editor/Volume/VolumeAdditionalGizmo.cs.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/CubemapRender.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ec83f2a0a0da1f0499e433a0070dfdb4 +guid: 4bde7a8c0f6757b48a734d0a21766a01 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs new file mode 100644 index 00000000000..5682cd2f4c0 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine.Rendering; + + +namespace UnityEngine.PathTracing.Core +{ + internal struct EnvironmentCDF + { + public GraphicsBuffer ConditionalBuffer; + public GraphicsBuffer MarginalBuffer; + public int ConditionalResolution; + public int MarginalResolution; + } + + internal class EnvironmentImportanceSampling : IDisposable + { + public EnvironmentImportanceSampling(ComputeShader shader) + { + _environmentCDF.MarginalResolution = 64; + _environmentCDF.ConditionalResolution = _environmentCDF.MarginalResolution * 2; + _environmentCDF.ConditionalBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _environmentCDF.ConditionalResolution * _environmentCDF.MarginalResolution, sizeof(float)); + _environmentCDF.MarginalBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _environmentCDF.MarginalResolution, sizeof(float)); + + _shader = shader; + if (_shader) + { + _computeConditionalKernel = _shader.FindKernel("ComputeConditional"); + _computeMarginalKernel = _shader.FindKernel("ComputeMarginal"); + } + } + + public void ComputeCDFBuffers(CommandBuffer cmd, Texture cubemap) + { + cmd.SetComputeIntParam(_shader, "_PathTracingSkyConditionalResolution", _environmentCDF.ConditionalResolution); + cmd.SetComputeIntParam(_shader, "_PathTracingSkyMarginalResolution", _environmentCDF.MarginalResolution); + + cmd.SetComputeTextureParam(_shader, _computeConditionalKernel, "_PathTracingSkybox", cubemap); + cmd.SetComputeBufferParam(_shader, _computeConditionalKernel, "_PathTracingSkyConditionalBuffer", _environmentCDF.ConditionalBuffer); + cmd.SetComputeBufferParam(_shader, _computeConditionalKernel, "_PathTracingSkyMarginalBuffer", _environmentCDF.MarginalBuffer); + cmd.DispatchCompute(_shader, _computeConditionalKernel, 1, _environmentCDF.MarginalResolution, 1); + + cmd.SetComputeBufferParam(_shader, _computeMarginalKernel, "_PathTracingSkyMarginalBuffer", _environmentCDF.MarginalBuffer); + cmd.DispatchCompute(_shader, _computeMarginalKernel, 1, 1, 1); + } + + internal EnvironmentCDF GetSkyboxCDF() + { + return _environmentCDF; + } + + public void Dispose() + { + _environmentCDF.ConditionalBuffer.Dispose(); + _environmentCDF.MarginalBuffer.Dispose(); + } + + private readonly ComputeShader _shader; + private readonly int _computeConditionalKernel; + private readonly int _computeMarginalKernel; + private readonly EnvironmentCDF _environmentCDF; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs.meta new file mode 100644 index 00000000000..e0de27f07de --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Environment/EnvironmentImportanceSampling.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1c1174c4c735e6b45bb06384a3c08e2d \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs new file mode 100644 index 00000000000..8e63249f99f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Unity.Collections.LowLevel.Unsafe; + +namespace UnityEngine.PathTracing.Core +{ + // The type parameter T is only used as a tag, preventing different kinds of handles from being mixed together. + internal readonly struct Handle + { + public readonly UInt64 Value; + + public Handle(UInt64 value) + { + Value = value; + } + + public static readonly Handle Invalid = new(0xFFFFFFFFFFFFFFFF); + + public bool IsValid() => Value >= 0; + + internal int ToInt() + { + Debug.Assert(UnsafeUtility.SizeOf() == sizeof(int), + "If this assert is firing, the size of EntityId has changed. This function should no longer be used."); + + return (int)Value; + } + + // Value type semantics + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => obj is Handle other && other.Value == Value; + public override string ToString() => $"Handle<{typeof(T).Name}>({Value})"; + public static bool operator ==(Handle a, Handle b) => a.Value == b.Value; + public static bool operator !=(Handle a, Handle b) => a.Value != b.Value; + } + + // Keeps track of allocated instance handles. Reuses freed handles. + internal class HandleSet + { + private readonly Stack> _freeHandles = new(); + private UInt64 _nextHandleIndex; + + public Handle Add() + { + if (_freeHandles.Count > 0) + return _freeHandles.Pop(); + + return new Handle(_nextHandleIndex++); + } + + public void Remove(Handle handle) + { + Debug.Assert(!_freeHandles.Contains(handle)); + _freeHandles.Push(handle); + } + + public void Clear() + { + _freeHandles.Clear(); + _nextHandleIndex = 0; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs.meta new file mode 100644 index 00000000000..def9d3c841a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/HandleSet.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 79550f93c7dc21e41b0af2097cf46b53 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs new file mode 100644 index 00000000000..be8885699c3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; + +namespace UnityEngine.PathTracing.Core +{ + internal struct LightFalloffDesc + { + public float LUTRange; + public Experimental.GlobalIllumination.FalloffType FalloffType; + public readonly override int GetHashCode() + { + return HashCode.Combine(LUTRange, FalloffType); + } + } + + internal class LightFalloffLUT + { + // Inverse squared falloff: minimum distance to light to avoid division by zero + public const float DistThresholdSqr = 0.0001f; // 1cm (in Unity 1 is 1m) so this is 0.01^2 + + // Legacy Unity falloff: where the falloff down to zero should start + private const float ToZeroFadeStart = 0.8f * 0.8f; + + // Legacy Unity falloff: constants for OpenGL attenuation + private const float ConstantFac = 1.000f; + private const float QuadraticFac = 25.0f; + + // Calculate the quadratic attenuation factor for a light with a specified range + private static float CalculateLightQuadFac(float range) + { + return QuadraticFac / (range * range); + } + + private static float LightAttenuateNormalized(float distSqr) + { + // match the vertex lighting falloff + float atten = 1 / (ConstantFac + CalculateLightQuadFac(1.0f) * distSqr); + + // ...but vertex one does not falloff to zero at light's range; + // So force it to falloff to zero at the edges. + if (distSqr >= ToZeroFadeStart) + { + if (distSqr > 1) + atten = 0; + else + atten *= 1 - (distSqr - ToZeroFadeStart) / (1 - ToZeroFadeStart); + } + + return atten; + } + + public static float LegacyUnityFalloff(float normalizedDistance) + { + float clampedDist = math.clamp(normalizedDistance, 0.0f, 1.0f); + return LightAttenuateNormalized(clampedDist * clampedDist); + } + + public static float SmoothDistanceAttenuation(float squaredDistance, float invSqrAttenuationRadius) + { + float factor = squaredDistance * invSqrAttenuationRadius; + float smoothFactor = math.saturate(1.0f - factor * factor); + return smoothFactor * smoothFactor; + } + + public static float InverseSquaredFalloffSmooth(float squaredDistance, float invSqrAttenuationRadius) + { + float attenuation = 1.0f / (math.max(DistThresholdSqr, squaredDistance)); + // Non physically based hack to limit light influence to attenuationRadius. As we approach the range we fade out the light. + return attenuation * SmoothDistanceAttenuation(squaredDistance, invSqrAttenuationRadius); + } + + public static float InverseSquaredFalloff(float squaredDistance) + { + return 1.0f / (math.max(DistThresholdSqr, squaredDistance)); + } + + public static float[] BuildLightFalloffLUTs(LightFalloffDesc[] lightFalloffDescs, uint lightFalloffLUTLength = 1024) + { + List lightFalloffData = new(); + foreach (var lightFalloffDesc in lightFalloffDescs) + { + float range = lightFalloffDesc.LUTRange; + switch (lightFalloffDesc.FalloffType) + { + case Experimental.GlobalIllumination.FalloffType.InverseSquaredNoRangeAttenuation: + { + for (uint k = 0; k < lightFalloffLUTLength; ++k) + { + float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1); + float distance = range * normalizedTableDistance; + float value = InverseSquaredFalloff(distance * distance); + lightFalloffData.Add(value); + } + } + break; + case Experimental.GlobalIllumination.FalloffType.InverseSquared: + { + float invSqrAttenuationRadius = 1.0f / math.max(DistThresholdSqr, range * range); + for (uint k = 0; k < lightFalloffLUTLength; ++k) + { + float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1); + float distance = range * normalizedTableDistance; + float value = InverseSquaredFalloffSmooth(distance * distance, invSqrAttenuationRadius); + lightFalloffData.Add(value); + } + } + break; + case Experimental.GlobalIllumination.FalloffType.Linear: + { + for (uint k = 0; k < lightFalloffLUTLength; ++k) + { + float linear = 1.0f - ((float)k / (float)(lightFalloffLUTLength - 1)); + lightFalloffData.Add(linear); + } + } + break; + case Experimental.GlobalIllumination.FalloffType.Legacy: + default: + { + for (uint k = 0; k < lightFalloffLUTLength; ++k) + { + float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1); + float value = LegacyUnityFalloff(normalizedTableDistance); + lightFalloffData.Add(value); + } + } + break; + } + // Change the last value to 0 to limit the light influence to the range + lightFalloffData[^1] = 0.0f; + } + return lightFalloffData.ToArray(); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs.meta new file mode 100644 index 00000000000..fb313686396 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightFalloffLUT.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4d731376742bf4a49a65a6083a9b9b32 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs new file mode 100644 index 00000000000..e25830b0c5a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs @@ -0,0 +1,257 @@ +using Unity.Mathematics; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; +using static UnityEngine.PathTracing.Lightmapping.LightmapIntegrationHelpers; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal static class ExpansionShaderIDs + { + public static readonly int GBuffer = Shader.PropertyToID("g_GBuffer"); + public static readonly int DestinationTexture = Shader.PropertyToID("g_DestinationTexture"); + public static readonly int DestinationX = Shader.PropertyToID("g_DestinationX"); + public static readonly int DestinationY = Shader.PropertyToID("g_DestinationY"); + public static readonly int ExpandedTexelSampleWidth = Shader.PropertyToID("g_ExpandedTexelSampleWidth"); + public static readonly int Float4Buffer = Shader.PropertyToID("g_Float4Buffer"); + public static readonly int Float4BufferLength = Shader.PropertyToID("g_Float4BufferLength"); + public static readonly int BinaryBufferSize = Shader.PropertyToID("g_BinaryBufferSize"); + public static readonly int BinaryGroupSize = Shader.PropertyToID("g_BinaryGroupSize"); + public static readonly int SourceBuffer = Shader.PropertyToID("g_SourceBuffer"); + public static readonly int SourceStride = Shader.PropertyToID("g_SourceStride"); + public static readonly int GBufferLength = Shader.PropertyToID("g_GBufferLength"); + public static readonly int CompactedGBuffer = Shader.PropertyToID("g_CompactedGBuffer"); + public static readonly int CompactedGBufferLength = Shader.PropertyToID("g_CompactedGBufferLength"); + public static readonly int InstanceWidth = Shader.PropertyToID("g_InstanceWidth"); + public static readonly int ThreadGroupSizeX = Shader.PropertyToID("g_ThreadGroupSizeX"); + public static readonly int AccumulationDispatchBuffer = Shader.PropertyToID("g_AccumulationDispatchBuffer"); + public static readonly int ClearDispatchBuffer = Shader.PropertyToID("g_ClearDispatchBuffer"); + public static readonly int CopyDispatchBuffer = Shader.PropertyToID("g_CopyDispatchBuffer"); + public static readonly int ReduceDispatchBuffer = Shader.PropertyToID("g_ReduceDispatchBuffer"); + public static readonly int ChunkSize = Shader.PropertyToID("g_ChunkSize"); + public static readonly int ChunkOffsetX = Shader.PropertyToID("g_ChunkOffsetX"); + public static readonly int ChunkOffsetY = Shader.PropertyToID("g_ChunkOffsetY"); + public static readonly int PassSampleCount = Shader.PropertyToID("g_PassSampleCount"); + public static readonly int SampleOffset = Shader.PropertyToID("g_SampleOffset"); + } + + internal static class ExpansionHelpers + { + static internal int PopulateAccumulationIndirectDispatch(CommandBuffer cmd, IRayTracingShader accumulationShader, ComputeShader populateShader, int populateKernel, uint expandedSampleWidth, GraphicsBuffer compactedGbufferLength, GraphicsBuffer accumulationDispatchBuffer) + { + cmd.SetComputeIntParam(populateShader, ExpansionShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + cmd.SetComputeBufferParam(populateShader, populateKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGbufferLength); + cmd.SetComputeBufferParam(populateShader, populateKernel, ExpansionShaderIDs.AccumulationDispatchBuffer, accumulationDispatchBuffer); + cmd.DispatchCompute(populateShader, populateKernel, 1, 1, 1); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, accumulationDispatchBuffer, "accumulationDispatchBuffer", LogBufferType.UInt); + return 1; + } + + static internal int PopulateClearExpandedOutputIndirectDispatch(CommandBuffer cmd, ComputeShader populateClearDispatch, int populateClearDispatchKernel, uint clearThreadGroupSizeX, uint expandedSampleWidth, GraphicsBuffer compactedGBufferLength, GraphicsBuffer clearDispatchBuffer) + { + // Populate the expanded clear indirect dispatch buffer - using the compacted size. + cmd.SetComputeIntParam(populateClearDispatch, ExpansionShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + cmd.SetComputeIntParam(populateClearDispatch, ExpansionShaderIDs.ThreadGroupSizeX, (int)clearThreadGroupSizeX); + cmd.SetComputeBufferParam(populateClearDispatch, populateClearDispatchKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + cmd.SetComputeBufferParam(populateClearDispatch, populateClearDispatchKernel, ExpansionShaderIDs.ClearDispatchBuffer, clearDispatchBuffer); + cmd.DispatchCompute(populateClearDispatch, populateClearDispatchKernel, 1, 1, 1); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, clearDispatchBuffer, "clearDispatchBuffer", LogBufferType.UInt); + return 1; + } + + static internal int ClearExpandedOutput(CommandBuffer cmd, ComputeShader clearExpandedOutput, int clearExpandedOutputKernel, GraphicsBuffer expandedOutput, GraphicsBuffer clearDispatchBuffer) + { + Debug.Assert(expandedOutput.stride == 16); + // Clear the output buffers. + cmd.SetComputeIntParam(clearExpandedOutput, ExpansionShaderIDs.Float4BufferLength, expandedOutput.count); + cmd.SetComputeBufferParam(clearExpandedOutput, clearExpandedOutputKernel, ExpansionShaderIDs.Float4Buffer, expandedOutput); + cmd.BeginSample("Clear (Expanded)"); + cmd.DispatchCompute(clearExpandedOutput, clearExpandedOutputKernel, clearDispatchBuffer, 0); + cmd.EndSample("Clear (Expanded)"); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, expandedOutput, "expandedOutput", LogBufferType.Float4); + return 1; + } + + static internal void GenerateGBuffer( + CommandBuffer cmd, + IRayTracingShader gBufferShader, + GraphicsBuffer gBuffer, + GraphicsBuffer traceScratchBuffer, + SamplingResources samplingResources, + UVAccelerationStructure uvAS, + UVFallbackBuffer uvFallbackBuffer, + GraphicsBuffer compactedGBufferLength, + GraphicsBuffer compactedTexelIndices, + Vector2Int instanceTexelOffset, + uint2 chunkOffset, + uint chunkSize, + uint expandedSampleWidth, + uint passSampleCount, + uint sampleOffset, + AntiAliasingType aaType, + uint superSampleWidth // the width of the superSampleWidth x superSampleWidth grid used when not using stochastic anti-aliasing + ) + { + var stochasticAntiAliasing = aaType == AntiAliasingType.Stochastic; + + // bind buffers + gBufferShader.SetAccelerationStructure(cmd, "g_UVAccelStruct", uvAS._uvAS); + gBufferShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + SamplingResources.Bind(cmd, samplingResources); + uvFallbackBuffer.BindChunked(cmd, gBufferShader, instanceTexelOffset, chunkOffset, chunkSize); + gBufferShader.SetBufferParam(cmd, ExpansionShaderIDs.CompactedGBuffer, compactedTexelIndices); + gBufferShader.SetBufferParam(cmd, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + gBufferShader.SetIntParam(cmd, ExpansionShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + gBufferShader.SetIntParam(cmd, ExpansionShaderIDs.PassSampleCount, (int)passSampleCount); + gBufferShader.SetIntParam(cmd, ExpansionShaderIDs.SampleOffset, (int)sampleOffset); + + // set antialiasing parameters + gBufferShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.StochasticAntialiasing, stochasticAntiAliasing ? 1 : 0); + gBufferShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SuperSampleWidth, (int)superSampleWidth); + + // dispatch the shader + Debug.Assert(gBufferShader is HardwareRayTracingShader || GraphicsHelpers.DivUp((int)chunkSize, gBufferShader.GetThreadGroupSizes().x) < 65536, "Chunk size is too large for the shader to handle."); + cmd.BeginSample("UV Sampling"); + gBufferShader.Dispatch(cmd, traceScratchBuffer, chunkSize * expandedSampleWidth, 1, 1); + cmd.EndSample("UV Sampling"); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, gBuffer, "gBuffer", LogBufferType.HitEntry); + } + + static internal float2[] DebugGBuffer(CommandBuffer cmd, BakeInstance instance, LightmappingContext lightmappingContext, uint expandedSampleWidth, uint passSampleCount) + { + // Get the length of the compacted GBuffer length to know how many samples there are. + uint[] compactedLength = new uint[1]; + GraphicsHelpers.Flush(cmd); + lightmappingContext.IntegratorContext.CompactedGBufferLength.GetData(compactedLength); + uint sampleCount = compactedLength[0] * expandedSampleWidth; + + // Run the debug shader to retrieve the samples. + var lightmapSamplesExpanded = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(sampleCount), sizeof(float) * 2); + var instanceGeometryIndex = lightmappingContext.World.PathTracingWorld.GetAccelerationStructure().GeometryPool.GetInstanceGeometryIndex(instance.Mesh); + lightmappingContext.IntegratorContext.GBufferDebugShader.Accumulate( + cmd, + instance.LocalToWorldMatrix, + instance.LocalToWorldMatrixNormals, + instanceGeometryIndex, + lightmappingContext.World.PathTracingWorld, + lightmappingContext.GBuffer, + expandedSampleWidth, + lightmapSamplesExpanded, + lightmappingContext.IntegratorContext.CompactedGBufferLength + ); + GraphicsHelpers.Flush(cmd); + + // Retrieve the samples from the GPU. + float2[] uvSampleData = new float2[sampleCount]; + lightmapSamplesExpanded.GetData(uvSampleData, 0, 0, (int)sampleCount); + lightmapSamplesExpanded.Dispose(); + + // Filter the samples + float4 scaleAndOffset = instance.SourceLightmapST; + System.Collections.Generic.List filteredSamples = new System.Collections.Generic.List(); + for (int i = 0; i < uvSampleData.Length; ++i) + { + // For some passes the expanded buffer is not filled completely and we need to account for that. + var localSampleIndex = i % expandedSampleWidth; + if (localSampleIndex < passSampleCount) + { + // this is a valid sample - apply lightmap ST to map into lightmap space + var sample = uvSampleData[i]; + var transformedUV = sample * scaleAndOffset.xy + scaleAndOffset.zw; + filteredSamples.Add(transformedUV); + } + } + + return filteredSamples.ToArray(); + } + + static internal int CompactGBuffer(CommandBuffer cmd, ComputeShader compactGBuffer, int compactGBufferKernel, uint instanceWidth, uint chunkSize, uint2 chunkOffset, UVFallbackBuffer uvFallbackBuffer, GraphicsBuffer compactedGBufferLength, GraphicsBuffer compactedTexelIndices) + { + compactGBuffer.GetKernelThreadGroupSizes(compactGBufferKernel, out uint gbuf_x, out _, out _); + cmd.SetBufferData(compactedGBufferLength, new uint[] { 0 }); + cmd.SetComputeIntParam(compactGBuffer, ExpansionShaderIDs.ChunkSize, (int)chunkSize); + cmd.SetComputeIntParam(compactGBuffer, ExpansionShaderIDs.InstanceWidth, (int)instanceWidth); + cmd.SetComputeIntParam(compactGBuffer, ExpansionShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + cmd.SetComputeIntParam(compactGBuffer, ExpansionShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + cmd.SetComputeTextureParam(compactGBuffer, compactGBufferKernel, UVFallbackBufferBuilderShaderIDs.UvFallback, uvFallbackBuffer.UVFallbackRT); + cmd.SetComputeBufferParam(compactGBuffer, compactGBufferKernel, ExpansionShaderIDs.CompactedGBuffer, compactedTexelIndices); + cmd.SetComputeBufferParam(compactGBuffer, compactGBufferKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + cmd.BeginSample("Compact GBuffer"); + cmd.DispatchCompute(compactGBuffer, compactGBufferKernel, GraphicsHelpers.DivUp((int)chunkSize, gbuf_x), 1, 1); + cmd.EndSample("Compact GBuffer"); + + //System.Console.WriteLine($"compactGBufferThreadGroupSizeX: {gbuf_x}"); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, compactedGBufferLength, "compactedGBufferLength", LogBufferType.UInt); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, compactedTexelIndices, "compactedTexelIndices", LogBufferType.UInt); + return 1; + } + + static internal int PopulateReduceExpandedOutputIndirectDispatch(CommandBuffer cmd, ComputeShader populateReduceExpandedOutput, int populateReduceExpandedOutputKernel, uint reduceThreadGroupSizeX, uint expandedSampleWidth, GraphicsBuffer compactedGBufferLength, GraphicsBuffer reduceDispatchBuffer) + { + // Populate the reduce copy indirect dispatch buffer - using the compacted size. + cmd.SetComputeIntParam(populateReduceExpandedOutput, ExpansionShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + cmd.SetComputeIntParam(populateReduceExpandedOutput, ExpansionShaderIDs.ThreadGroupSizeX, (int)reduceThreadGroupSizeX); + cmd.SetComputeBufferParam(populateReduceExpandedOutput, populateReduceExpandedOutputKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + cmd.SetComputeBufferParam(populateReduceExpandedOutput, populateReduceExpandedOutputKernel, ExpansionShaderIDs.ReduceDispatchBuffer, reduceDispatchBuffer); + cmd.DispatchCompute(populateReduceExpandedOutput, populateReduceExpandedOutputKernel, 1, 1, 1); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, reduceDispatchBuffer, "reduceDispatchBuffer"); + return 1; + } + + static internal int ReduceExpandedOutput(CommandBuffer cmd, ComputeShader binaryGroupSumLeftShader, int binaryGroupSumLeftKernel, GraphicsBuffer expandedOutput, int expandedDispatchSize, uint expandedSampleWidth, GraphicsBuffer reduceDispatch) + { + Debug.Assert(math.ispow2(expandedSampleWidth)); + + // the expandedOutput buffer contains groups of expandedSampleWidth samples + // do binary summation within the sample groups ending up with summed samples in the leftmost sample for each texel + cmd.SetComputeBufferParam(binaryGroupSumLeftShader, binaryGroupSumLeftKernel, ExpansionShaderIDs.Float4Buffer, expandedOutput); + cmd.SetComputeIntParam(binaryGroupSumLeftShader, ExpansionShaderIDs.BinaryBufferSize, expandedDispatchSize); + int groupSize = 1; + int dispatches = 0; + while (groupSize < expandedSampleWidth) + { + cmd.SetComputeIntParam(binaryGroupSumLeftShader, ExpansionShaderIDs.BinaryGroupSize, groupSize); + cmd.BeginSample("Binary Sum"); + cmd.DispatchCompute(binaryGroupSumLeftShader, binaryGroupSumLeftKernel, reduceDispatch, 0); + cmd.EndSample("Binary Sum"); + groupSize *= 2; + dispatches++; + } + return dispatches; + } + + + static internal int PopulateCopyToLightmapIndirectDispatch(CommandBuffer cmd, ComputeShader populateCopyToLightmap, int populateCopyToLightmapKernel, uint copyThreadGroupSizeX, GraphicsBuffer compactedGBufferLength, GraphicsBuffer copyDispatch) + { + // Populate the expanded copy indirect dispatch buffer - using the compacted size. + cmd.SetComputeIntParam(populateCopyToLightmap, ExpansionShaderIDs.ThreadGroupSizeX, (int)copyThreadGroupSizeX); + cmd.SetComputeBufferParam(populateCopyToLightmap, populateCopyToLightmapKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + cmd.SetComputeBufferParam(populateCopyToLightmap, populateCopyToLightmapKernel, ExpansionShaderIDs.CopyDispatchBuffer, copyDispatch); + cmd.DispatchCompute(populateCopyToLightmap, populateCopyToLightmapKernel, 1, 1, 1); + //LightmapIntegrationHelpers.LogGraphicsBuffer(cmd, copyDispatch, "copyDispatch"); + return 1; + } + + static internal int CopyToLightmap(CommandBuffer cmd, ComputeShader copyToLightmap, int copyToLightmapKernel, uint expandedSampleWidth, int instanceWidth, Vector2Int instanceTexelOffset, uint2 chunkOffset, GraphicsBuffer compactedGBufferLength, GraphicsBuffer compactedTexelIndices, GraphicsBuffer expandedOutput, GraphicsBuffer copyDispatch, RenderTexture output) + { + cmd.SetComputeBufferParam(copyToLightmap, copyToLightmapKernel, ExpansionShaderIDs.SourceBuffer, expandedOutput); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.SourceStride, (int)expandedSampleWidth); + cmd.SetComputeTextureParam(copyToLightmap, copyToLightmapKernel, ExpansionShaderIDs.DestinationTexture, output); + cmd.SetComputeBufferParam(copyToLightmap, copyToLightmapKernel, ExpansionShaderIDs.CompactedGBuffer, compactedTexelIndices); + cmd.SetComputeBufferParam(copyToLightmap, copyToLightmapKernel, ExpansionShaderIDs.CompactedGBufferLength, compactedGBufferLength); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.InstanceWidth, instanceWidth); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.DestinationX, instanceTexelOffset.x); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.DestinationY, instanceTexelOffset.y); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + cmd.SetComputeIntParam(copyToLightmap, ExpansionShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + copyToLightmap.GetKernelThreadGroupSizes(copyToLightmapKernel, out uint copyx, out uint _, out _); + cmd.BeginSample("Copy to Lightmap"); + cmd.DispatchCompute(copyToLightmap, copyToLightmapKernel, copyDispatch, 0); + cmd.EndSample("Copy to Lightmap"); + return 1; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs.meta new file mode 100644 index 00000000000..26c1df14bab --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapExpansion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96b2515d277249b4586313e2b9578b83 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs new file mode 100644 index 00000000000..7c31a3698f2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs @@ -0,0 +1,777 @@ +using Unity.Mathematics; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Integration +{ + internal enum AntiAliasingType { Stochastic, SuperSampling }; + + internal static class LightmapIntegratorShaderIDs + { + public static readonly int LightmapInOut = Shader.PropertyToID("g_LightmapInOut"); + public static readonly int DirectionalInOut = Shader.PropertyToID("g_DirectionalInOut"); + public static readonly int AdaptiveInOut = Shader.PropertyToID("g_AdaptiveInOut"); + public static readonly int AdaptiveSampling = Shader.PropertyToID("g_AdaptiveSampling"); + public static readonly int AdaptiveStopSamples = Shader.PropertyToID("g_AdaptiveStopSamples"); + public static readonly int AdaptiveCheckIfFullyConverged = Shader.PropertyToID("g_AdaptiveCheckIfFullyConverged"); + public static readonly int AdaptiveThreshold = Shader.PropertyToID("g_AdaptiveThreshold"); + public static readonly int AccumulateDirectional = Shader.PropertyToID("g_AccumulateDirectional"); + public static readonly int SampleCountInOut = Shader.PropertyToID("g_SampleCountInOut"); + public static readonly int SampleCountIn = Shader.PropertyToID("g_SampleCountIn"); + public static readonly int ShaderLocalToWorld = Shader.PropertyToID("g_ShaderLocalToWorld"); + public static readonly int ShaderLocalToWorldNormals = Shader.PropertyToID("g_ShaderLocalToWorldNormals"); + public static readonly int InstanceGeometryIndex = Shader.PropertyToID("g_InstanceGeometryIndex"); + public static readonly int GISampleCount = Shader.PropertyToID("g_GISampleCount"); + public static readonly int AOMaxDistance = Shader.PropertyToID("g_AOMaxDistance"); + public static readonly int InputSampleCountInW = Shader.PropertyToID("g_InputSampleCountInW"); + public static readonly int Normals = Shader.PropertyToID("g_Normals"); + public static readonly int TextureWidth = Shader.PropertyToID("g_TextureWidth"); + public static readonly int TextureHeight = Shader.PropertyToID("g_TextureHeight"); + public static readonly int ReceiveShadows = Shader.PropertyToID("g_ReceiveShadows"); + public static readonly int PushOff = Shader.PropertyToID("g_PushOff"); + public static readonly int IndirectDispatchDimensions = Shader.PropertyToID("g_IndirectDispatchDimensions"); + public static readonly int IndirectDispatchoriginalDimensions = Shader.PropertyToID("g_IndirectDispatchOriginalDimensions"); + public static readonly int InputBufferSelector = Shader.PropertyToID("g_InputBufferSelector"); + public static readonly int InputBufferLength = Shader.PropertyToID("g_InputBufferLength"); + public static readonly int InputBuffer0 = Shader.PropertyToID("g_InputBuffer0"); + public static readonly int InputBuffer1 = Shader.PropertyToID("g_InputBuffer1"); + public static readonly int SelectionOutput = Shader.PropertyToID("g_SelectionOutput"); + public static readonly int GBuffer = Shader.PropertyToID("g_GBuffer"); + public static readonly int SampleOffset = Shader.PropertyToID("g_SampleOffset"); + public static readonly int StochasticAntialiasing = Shader.PropertyToID("g_StochasticAntialiasing"); + public static readonly int SuperSampleWidth = Shader.PropertyToID("g_SuperSampleWidth"); + public static readonly int Float3Buffer = Shader.PropertyToID("g_Float3Buffer"); + public static readonly int DestinationTexture = Shader.PropertyToID("g_DestinationTexture"); + public static readonly int SourceTexture = Shader.PropertyToID("g_SourceTexture"); + public static readonly int SourceX = Shader.PropertyToID("g_SourceX"); + public static readonly int SourceY = Shader.PropertyToID("g_SourceY"); + public static readonly int SourceWidth = Shader.PropertyToID("g_SourceWidth"); + public static readonly int SourceHeight = Shader.PropertyToID("g_SourceHeight"); + public static readonly int DestinationX = Shader.PropertyToID("g_DestinationX"); + public static readonly int DestinationY = Shader.PropertyToID("g_DestinationY"); + public static readonly int TextureInOut = Shader.PropertyToID("g_TextureInOut"); + public static readonly int ExpandedOutput = Shader.PropertyToID("g_ExpandedOutput"); + public static readonly int ExpandedDirectional = Shader.PropertyToID("g_ExpandedDirectional"); + public static readonly int ExpandedSampleCountInW = Shader.PropertyToID("g_ExpandedSampleCountInW"); + public static readonly int ExpandedTexelSampleWidth = Shader.PropertyToID("g_ExpandedTexelSampleWidth"); + public static readonly int MaxLocalSampleCount = Shader.PropertyToID("g_MaxLocalSampleCount"); + public static readonly int SourceBuffer = Shader.PropertyToID("g_SourceBuffer"); + public static readonly int SourceLength = Shader.PropertyToID("g_SourceLength"); + public static readonly int SourceStride = Shader.PropertyToID("g_SourceStride"); + public static readonly int GBufferLength = Shader.PropertyToID("g_GBufferLength"); + public static readonly int CompactedGBuffer = Shader.PropertyToID("g_CompactedGBuffer"); + public static readonly int InstanceWidth = Shader.PropertyToID("g_InstanceWidth"); + public static readonly int ChunkOffsetX = Shader.PropertyToID("g_ChunkOffsetX"); + public static readonly int ChunkOffsetY = Shader.PropertyToID("g_ChunkOffsetY"); + public static readonly int LightmapSamplesExpanded = Shader.PropertyToID("g_LightmapSamplesExpanded"); + } + + internal class LightmapOccupancyIntegrator + { + private ComputeShader _occupancyShader; + private int _occupancyKernel; + + public void Prepare(ComputeShader occupancyShader) + { + _occupancyShader = occupancyShader; + Debug.Assert(_occupancyShader != null); + _occupancyKernel = _occupancyShader.FindKernel("BlitOccupancy"); + } + + public void Accumulate( + CommandBuffer cmd, + Vector2Int instanceTexelSize, + Vector2Int instanceTexelOffset, + UVFallbackBuffer uvFallbackBuffer, + RenderTexture output) + { + uvFallbackBuffer.Bind(cmd, _occupancyShader, _occupancyKernel, instanceTexelOffset); + cmd.SetComputeTextureParam(_occupancyShader, _occupancyKernel, LightmapIntegratorShaderIDs.LightmapInOut, output); + _occupancyShader.GetKernelThreadGroupSizes(_occupancyKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_occupancyShader, _occupancyKernel, GraphicsHelpers.DivUp(instanceTexelSize.x, x), GraphicsHelpers.DivUp(instanceTexelSize.y, y), 1); + } + } + + internal class LightmapDirectIntegrator : System.IDisposable + { + private IRayTracingShader _accumulationShader; + private ComputeShader _normalizationShader; + private int _normalizationKernel; + private int _directionalNormalizationKernel; + private SamplingResources _samplingResources; + private RTHandle _emptyTexture; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public LightmapDirectIntegrator() + { + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader normalizationShader, ComputeShader expansionHelpers, SamplingResources samplingResources, RTHandle emptyExposureTexture) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _normalizationShader = normalizationShader; + Debug.Assert(_normalizationShader != null); + _normalizationKernel = _normalizationShader.FindKernel("NormalizeRadiance"); + _directionalNormalizationKernel = _normalizationShader.FindKernel("NormalizeDirection"); + + _samplingResources = samplingResources; + _emptyTexture = emptyExposureTexture; + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + uint sampleCountToTakePerTexel, + uint currentSampleCountPerTexel, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + Vector2Int instanceTexelSize, + uint2 chunkOffset, + World world, + GraphicsBuffer traceScratchBuffer, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer expandedOutput, + GraphicsBuffer expandedDirectional, + GraphicsBuffer compactedTexelIndices, + GraphicsBuffer compactedGbufferLength, + bool receiveShadows, + float pushOff, + uint lightEvaluationsPerBounce, + bool newChunkStarted) + { + bool doDirectional = expandedDirectional != null; + int instanceWidth = instanceTexelSize.x; + int instanceHeight = instanceTexelSize.y; + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(gBuffer.count == expandedOutput.count); + Debug.Assert(sampleCountToTakePerTexel <= expandedSampleWidth); + Debug.Assert(sampleCountToTakePerTexel > 0); + + // path tracing inputs + bool preExpose = false; + float environmentIntensityMultiplier = 1.0f; + Util.BindPathTracingInputs(cmd, _accumulationShader, false, lightEvaluationsPerBounce, preExpose, 0, environmentIntensityMultiplier, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyTexture); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + var requiredSizeInBytes = _accumulationShader.GetTraceScratchBufferRequiredSizeInBytes((uint)expandedOutput.count, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(traceScratchBuffer.count * traceScratchBuffer.stride); + Debug.Assert(traceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ReceiveShadows, receiveShadows ? 1 : 0); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.PushOff, pushOff); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, instanceWidth); + + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.AccumulateDirectional, doDirectional ? 1 : 0); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, compactedTexelIndices); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, expandedOutput); + if (doDirectional) + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedDirectional, expandedDirectional); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + if (newChunkStarted) + { + // Its time to repopulate the indirect dispatch buffers (as a new instance has started). Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + } + + // accumulate (expanded) + { + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, (int)currentSampleCountPerTexel); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, (int)sampleCountToTakePerTexel); + cmd.BeginSample("Accumulation (Expanded)"); + _accumulationShader.Dispatch(cmd, traceScratchBuffer, _accumulationDispatchBuffer); + cmd.EndSample("Accumulation (Expanded)"); + } + } + + public void Normalize(CommandBuffer cmd, RenderTexture lightmapInOut) + { + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.LightmapInOut, lightmapInOut); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, lightmapInOut.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, lightmapInOut.height); + _normalizationShader.GetKernelThreadGroupSizes(_normalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _normalizationKernel, GraphicsHelpers.DivUp(lightmapInOut.width, x), GraphicsHelpers.DivUp(lightmapInOut.height, y), 1); + } + + public void NormalizeDirectional(CommandBuffer cmd, RenderTexture directionalInOut, RenderTexture sampleCountInW, RenderTexture normals) + { + Debug.Assert(directionalInOut.width == sampleCountInW.width && directionalInOut.height == sampleCountInW.height); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.DirectionalInOut, directionalInOut); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.InputSampleCountInW, sampleCountInW); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.Normals, normals); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, directionalInOut.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, directionalInOut.height); + _normalizationShader.GetKernelThreadGroupSizes(_directionalNormalizationKernel, out uint x, out uint y, out uint _); + cmd.DispatchCompute(_normalizationShader, _directionalNormalizationKernel, GraphicsHelpers.DivUp(directionalInOut.width, x), GraphicsHelpers.DivUp(directionalInOut.height, y), 1); + } + } + + internal class LightmapIndirectIntegrator : System.IDisposable + { + private IRayTracingShader _accumulationShader; + private ComputeShader _normalizationShader; + private int _normalizationKernel; + private int _directionalNormalizationKernel; + private SamplingResources _samplingResources; + private RTHandle _emptyTexture; + private bool _countNEERayAsPathSegment; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public LightmapIndirectIntegrator(bool countNEERayAsPathSegment) + { + _countNEERayAsPathSegment = countNEERayAsPathSegment; + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader normalizationShader, ComputeShader expansionHelpers, SamplingResources samplingResources, RTHandle emptyExposureTexture) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _normalizationShader = normalizationShader; + Debug.Assert(_normalizationShader != null); + _normalizationKernel = _normalizationShader.FindKernel("NormalizeRadiance"); + _directionalNormalizationKernel = _normalizationShader.FindKernel("NormalizeDirection"); + + _samplingResources = samplingResources; + _emptyTexture = emptyExposureTexture; + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + uint sampleCountToTakePerTexel, + uint currentSampleCountPerTexel, + uint bounceCount, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + Vector2Int instanceTexelSize, + uint2 chunkOffset, + World world, + GraphicsBuffer traceScratchBuffer, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer expandedOutput, + GraphicsBuffer expandedDirectional, + GraphicsBuffer compactedTexelIndices, + GraphicsBuffer compactedGbufferLength, + float pushOff, + uint lightEvaluationsPerBounce, + bool newChunkStarted) + { + bool doDirectional = expandedDirectional != null; + int instanceWidth = instanceTexelSize.x; + int instanceHeight = instanceTexelSize.y; + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(gBuffer.count == expandedOutput.count); + Debug.Assert(sampleCountToTakePerTexel <= expandedSampleWidth); + Debug.Assert(sampleCountToTakePerTexel > 0); + + // path tracing inputs + bool preExpose = false; + float environmentIntensityMultiplier = 1.0f; + Util.BindPathTracingInputs(cmd, _accumulationShader, _countNEERayAsPathSegment, lightEvaluationsPerBounce, preExpose, (int)bounceCount, environmentIntensityMultiplier, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyTexture); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + var requiredSizeInBytes = _accumulationShader.GetTraceScratchBufferRequiredSizeInBytes((uint)expandedOutput.count, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(traceScratchBuffer.count * traceScratchBuffer.stride); + Debug.Assert(traceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.PushOff, pushOff); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, instanceWidth); + + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.AccumulateDirectional, doDirectional ? 1 : 0); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, compactedTexelIndices); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, expandedOutput); + if (doDirectional) + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedDirectional, expandedDirectional); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + if (newChunkStarted) + { + // Its time to repopulate the indirect dispatch buffers (as a new instance has started). Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + } + + // accumulate (expanded) + { + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, (int)currentSampleCountPerTexel); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, (int)sampleCountToTakePerTexel); + cmd.BeginSample("Accumulation (Expanded)"); + _accumulationShader.Dispatch(cmd, traceScratchBuffer, _accumulationDispatchBuffer); + cmd.EndSample("Accumulation (Expanded)"); + } + } + + public void Normalize(CommandBuffer cmd, RenderTexture lightmapInOut) + { + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.LightmapInOut, lightmapInOut); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, lightmapInOut.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, lightmapInOut.height); + _normalizationShader.GetKernelThreadGroupSizes(_normalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _normalizationKernel, GraphicsHelpers.DivUp(lightmapInOut.width, x), GraphicsHelpers.DivUp(lightmapInOut.height, y), 1); + } + + public void NormalizeDirectional(CommandBuffer cmd, RenderTexture directionalInOut, RenderTexture sampleCountInW, RenderTexture normals) + { + Debug.Assert(directionalInOut.width == sampleCountInW.width && directionalInOut.height == sampleCountInW.height); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.DirectionalInOut, directionalInOut); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.InputSampleCountInW, sampleCountInW); + cmd.SetComputeTextureParam(_normalizationShader, _directionalNormalizationKernel, LightmapIntegratorShaderIDs.Normals, normals); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, directionalInOut.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, directionalInOut.height); + _normalizationShader.GetKernelThreadGroupSizes(_directionalNormalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _directionalNormalizationKernel, GraphicsHelpers.DivUp(directionalInOut.width, x), GraphicsHelpers.DivUp(directionalInOut.height, y), 1); + } + } + + internal class LightmapAOIntegrator : System.IDisposable + { + private IRayTracingShader _accumulationShader; + private ComputeShader _normalizationShader; + private int _normalizationKernel; + private SamplingResources _samplingResources; + private RTHandle _emptyTexture; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader normalizationShader, ComputeShader expansionHelpers, SamplingResources samplingResources, RTHandle emptyExposureTexture) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _normalizationShader = normalizationShader; + Debug.Assert(_normalizationShader != null); + _normalizationKernel = _normalizationShader.FindKernel("NormalizeAO"); + + _samplingResources = samplingResources; + _emptyTexture = emptyExposureTexture; + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + uint sampleCountToTakePerTexel, + uint currentSampleCountPerTexel, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + Vector2Int instanceTexelSize, + uint2 chunkOffset, + World world, + GraphicsBuffer traceScratchBuffer, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer expandedOutput, + GraphicsBuffer compactedTexelIndices, + GraphicsBuffer compactedGbufferLength, + float pushOff, + float aoMaxDistance, + bool newChunkStarted) + { + int instanceWidth = instanceTexelSize.x; + int instanceHeight = instanceTexelSize.y; + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(gBuffer.count == expandedOutput.count); + Debug.Assert(sampleCountToTakePerTexel <= expandedSampleWidth); + Debug.Assert(sampleCountToTakePerTexel > 0); + + // path tracing inputs + uint lightEvaluationsPerBounce = 1; + bool preExpose = false; + float environmentIntensityMultipler = 1.0f; + Util.BindPathTracingInputs(cmd, _accumulationShader, false, lightEvaluationsPerBounce, preExpose, 0, environmentIntensityMultipler, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyTexture); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + var requiredSizeInBytes = _accumulationShader.GetTraceScratchBufferRequiredSizeInBytes((uint)expandedOutput.count, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(traceScratchBuffer.count * traceScratchBuffer.stride); + Debug.Assert(traceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.PushOff, pushOff); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.AOMaxDistance, aoMaxDistance); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, instanceWidth); + + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, compactedTexelIndices); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, expandedOutput); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + if (newChunkStarted) + { + // Its time to repopulate the indirect dispatch buffers (as a new instance has started). Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + } + + // accumulate (expanded) + { + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, (int)currentSampleCountPerTexel); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, (int)sampleCountToTakePerTexel); + cmd.BeginSample("Accumulation (Expanded)"); + _accumulationShader.Dispatch(cmd, traceScratchBuffer, _accumulationDispatchBuffer); + cmd.EndSample("Accumulation (Expanded)"); + } + } + + public void Normalize(CommandBuffer cmd, RenderTexture lightmap) + { + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.LightmapInOut, lightmap); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, lightmap.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, lightmap.height); + _normalizationShader.GetKernelThreadGroupSizes(_normalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _normalizationKernel, GraphicsHelpers.DivUp(lightmap.width, x), GraphicsHelpers.DivUp(lightmap.height, y), 1); + } + } + + internal class LightmapValidityIntegrator : System.IDisposable + { + IRayTracingShader _accumulationShader; + ComputeShader _normalizationShader; + int _normalizationKernel; + SamplingResources _samplingResources; + RTHandle _emptyTexture; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader normalizationShader, ComputeShader expansionHelpers, SamplingResources samplingResources, RTHandle emptyExposureTexture) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _normalizationShader = normalizationShader; + Debug.Assert(_normalizationShader != null); + _normalizationKernel = _normalizationShader.FindKernel("NormalizeValidity"); + + _samplingResources = samplingResources; + _emptyTexture = emptyExposureTexture; + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + uint sampleCountToTakePerTexel, + uint currentSampleCountPerTexel, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + Vector2Int instanceTexelSize, + uint2 chunkOffset, + World world, + GraphicsBuffer traceScratchBuffer, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer expandedOutput, + GraphicsBuffer compactedTexelIndices, + GraphicsBuffer compactedGbufferLength, + float pushOff, + bool newChunkStarted) + { + int instanceWidth = instanceTexelSize.x; + int instanceHeight = instanceTexelSize.y; + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(gBuffer.count == expandedOutput.count); + Debug.Assert(sampleCountToTakePerTexel <= expandedSampleWidth); + Debug.Assert(sampleCountToTakePerTexel > 0); + + // path tracing inputs + uint lightEvaluationsPerBounce = 1; + bool preExpose = false; + float environmentIntensityMultiplier = 1.0f; + Util.BindPathTracingInputs(cmd, _accumulationShader, false, lightEvaluationsPerBounce, preExpose, 0, environmentIntensityMultiplier, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyTexture); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + var requiredSizeInBytes = _accumulationShader.GetTraceScratchBufferRequiredSizeInBytes((uint)expandedOutput.count, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(traceScratchBuffer.count * traceScratchBuffer.stride); + Debug.Assert(traceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.PushOff, pushOff); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, instanceWidth); + + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, compactedTexelIndices); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, expandedOutput); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + if (newChunkStarted) + { + // Its time to repopulate the indirect dispatch buffers (as a new instance has started). Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + } + + // accumulate (expanded) + { + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, (int)currentSampleCountPerTexel); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, (int)sampleCountToTakePerTexel); + cmd.BeginSample("Accumulation (Expanded)"); + _accumulationShader.Dispatch(cmd, traceScratchBuffer, _accumulationDispatchBuffer); + cmd.EndSample("Accumulation (Expanded)"); + } + } + + public void Normalize(CommandBuffer cmd, RenderTexture lightmapInOut) + { + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.LightmapInOut, lightmapInOut); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, lightmapInOut.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, lightmapInOut.height); + _normalizationShader.GetKernelThreadGroupSizes(_normalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _normalizationKernel, GraphicsHelpers.DivUp(lightmapInOut.width, x), GraphicsHelpers.DivUp(lightmapInOut.height, y), 1); + } + } + + internal class LightmapShadowMaskIntegrator : System.IDisposable + { + IRayTracingShader _accumulationShader; + ComputeShader _normalizationShader; + int _normalizationKernel; + SamplingResources _samplingResources; + RTHandle _emptyTexture; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader normalizationShader, ComputeShader expansionHelpers, SamplingResources samplingResources, RTHandle emptyExposureTexture) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _normalizationShader = normalizationShader; + Debug.Assert(_normalizationShader != null); + _normalizationKernel = _normalizationShader.FindKernel("NormalizeShadowMask"); + + _samplingResources = samplingResources; + _emptyTexture = emptyExposureTexture; + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + uint sampleCountToTakePerTexel, + uint currentSampleCountPerTexel, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + Vector2Int instanceTexelSize, + uint2 chunkOffset, + World world, + GraphicsBuffer traceScratchBuffer, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer expandedOutput, + GraphicsBuffer expandedSampleCountInW, + GraphicsBuffer compactedTexelIndices, + GraphicsBuffer compactedGbufferLength, + bool receiveShadows, + float pushOff, + uint lightEvaluationsPerBounce, + bool newChunkStarted) + { + int instanceWidth = instanceTexelSize.x; + int instanceHeight = instanceTexelSize.y; + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(gBuffer.count == expandedOutput.count); + Debug.Assert(sampleCountToTakePerTexel <= expandedSampleWidth); + Debug.Assert(sampleCountToTakePerTexel > 0); + + // path tracing inputs + bool preExpose = false; + float environmentIntensityMultiplier = 1.0f; + Util.BindPathTracingInputs(cmd, _accumulationShader, false, lightEvaluationsPerBounce, preExpose, 0, environmentIntensityMultiplier, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyTexture); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + var requiredSizeInBytes = _accumulationShader.GetTraceScratchBufferRequiredSizeInBytes((uint)expandedOutput.count, 1, 1); + if (requiredSizeInBytes > 0) + { + var actualScratchBufferSize = (ulong)(traceScratchBuffer.count * traceScratchBuffer.stride); + Debug.Assert(traceScratchBuffer.stride == sizeof(uint)); + Debug.Assert(requiredSizeInBytes <= actualScratchBufferSize); + } + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ReceiveShadows, receiveShadows ? 1 : 0); + _accumulationShader.SetFloatParam(cmd, LightmapIntegratorShaderIDs.PushOff, pushOff); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceWidth, instanceWidth); + + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.CompactedGBuffer, compactedTexelIndices); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedOutput, expandedOutput); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.ExpandedSampleCountInW, expandedSampleCountInW); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ExpandedTexelSampleWidth, (int)expandedSampleWidth); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + + if (newChunkStarted) + { + // Its time to repopulate the indirect dispatch buffers (as a new instance has started). Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + } + + // accumulate (expanded) + { + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.SampleOffset, (int)currentSampleCountPerTexel); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.MaxLocalSampleCount, (int)sampleCountToTakePerTexel); + cmd.BeginSample("Accumulation (Expanded)"); + _accumulationShader.Dispatch(cmd, traceScratchBuffer, _accumulationDispatchBuffer); + cmd.EndSample("Accumulation (Expanded)"); + } + } + + public void Normalize(CommandBuffer cmd, RenderTexture lightmap, RenderTexture sampleCountInW) + { + Debug.Assert(lightmap.width == sampleCountInW.width && lightmap.height == sampleCountInW.height); + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.LightmapInOut, lightmap); + cmd.SetComputeTextureParam(_normalizationShader, _normalizationKernel, LightmapIntegratorShaderIDs.InputSampleCountInW, sampleCountInW); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureWidth, lightmap.width); + cmd.SetComputeIntParam(_normalizationShader, LightmapIntegratorShaderIDs.TextureHeight, lightmap.height); + _normalizationShader.GetKernelThreadGroupSizes(_normalizationKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(_normalizationShader, _normalizationKernel, GraphicsHelpers.DivUp(lightmap.width, x), GraphicsHelpers.DivUp(lightmap.height, y), 1); + } + } + + internal class GBufferDebug : System.IDisposable + { + private IRayTracingShader _accumulationShader; + private GraphicsBuffer _accumulationDispatchBuffer; + private ComputeShader _expansionHelpers; + private int _populateAccumulationDispatchKernel; + + public void Dispose() + { + _accumulationDispatchBuffer?.Dispose(); + } + + public GBufferDebug() + { + } + + public void Prepare(IRayTracingShader accumulationShader, ComputeShader expansionHelpers) + { + _accumulationShader = accumulationShader; + Debug.Assert(_accumulationShader != null); + + _expansionHelpers = expansionHelpers; + _populateAccumulationDispatchKernel = _expansionHelpers.FindKernel("PopulateAccumulationDispatch"); + _accumulationDispatchBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + } + + public void Accumulate( + CommandBuffer cmd, + Matrix4x4 shaderLocalToWorld, + Matrix4x4 shaderLocalToWorldNormals, + int instanceGeometryIndex, + World world, + GraphicsBuffer gBuffer, + uint expandedSampleWidth, + GraphicsBuffer lightmapSamplesExpanded, + GraphicsBuffer compactedGbufferLength) + { + Debug.Assert(math.ispow2(expandedSampleWidth)); + Debug.Assert(lightmapSamplesExpanded.count <= gBuffer.count); + Debug.Assert(lightmapSamplesExpanded.count % expandedSampleWidth == 0); + Util.BindWorld(cmd, _accumulationShader, world, 1024); + + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorld, shaderLocalToWorld); + _accumulationShader.SetMatrixParam(cmd, LightmapIntegratorShaderIDs.ShaderLocalToWorldNormals, shaderLocalToWorldNormals); + _accumulationShader.SetIntParam(cmd, LightmapIntegratorShaderIDs.InstanceGeometryIndex, instanceGeometryIndex); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.GBuffer, gBuffer); + _accumulationShader.SetBufferParam(cmd, LightmapIntegratorShaderIDs.LightmapSamplesExpanded, lightmapSamplesExpanded); + + // Its time to repopulate the indirect dispatch buffers. Use the compacted size for this. + ExpansionHelpers.PopulateAccumulationIndirectDispatch(cmd, _accumulationShader, _expansionHelpers, _populateAccumulationDispatchKernel, expandedSampleWidth, compactedGbufferLength, _accumulationDispatchBuffer); + cmd.BeginSample("GBuffer Debug"); + _accumulationShader.Dispatch(cmd, null, _accumulationDispatchBuffer); + cmd.EndSample("GBuffer Debug"); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs.meta new file mode 100644 index 00000000000..3f0a3e78bc1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1b7f24ec37ad098488d9130f9203f68a \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs new file mode 100644 index 00000000000..e242bf10893 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs @@ -0,0 +1,633 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Unity.Mathematics; +using UnityEngine.Assertions; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal static class LightmapIntegrationHelpers + { + internal class ComputeHelpers + { + internal ComputeShader ComputeHelperShader = null; + + internal static class ShaderIDs + { + public static readonly int TextureInOut = Shader.PropertyToID("g_TextureInOut"); + public static readonly int SampleCountInW = Shader.PropertyToID("g_SampleCountInW"); + public static readonly int VarianceInR = Shader.PropertyToID("g_VarianceInR"); + public static readonly int StandardErrorInR = Shader.PropertyToID("g_StandardErrorInR"); + public static readonly int MeanInR = Shader.PropertyToID("g_MeanInR"); + public static readonly int SourceTexture = Shader.PropertyToID("g_SourceTexture"); + public static readonly int OutputBuffer = Shader.PropertyToID("g_OutputBuffer"); + public static readonly int X = Shader.PropertyToID("g_X"); + public static readonly int Y = Shader.PropertyToID("g_Y"); + public static readonly int TextureOut = Shader.PropertyToID("g_TextureOut"); + public static readonly int TextureWidth = Shader.PropertyToID("g_TextureWidth"); + public static readonly int TextureHeight = Shader.PropertyToID("g_TextureHeight"); + public static readonly int MultiplicationFactor = Shader.PropertyToID("g_MultiplicationFactor"); + public static readonly int BoxFilterRadius = Shader.PropertyToID("g_BoxFilterRadius"); + public static readonly int StandardErrorThreshold = Shader.PropertyToID("g_StandardErrorThreshold"); + public static readonly int ChannelIndex = Shader.PropertyToID("g_ChannelIndex"); + public static readonly int ChannelValue = Shader.PropertyToID("g_ChannelValue"); + public static readonly int TemporaryRenderTarget = Shader.PropertyToID("g_TemporaryRenderTarget"); + public static readonly int SecondTemporaryRenderTarget = Shader.PropertyToID("g_SecondTemporaryRenderTarget"); + public static readonly int MultiplyTemporaryRenderTarget = Shader.PropertyToID("g_MultiplyTemporaryRenderTarget"); + } + + internal static int MultiplyKernel; + internal static int BroadcastChannelKernel; + internal static int SetChannelKernel; + internal static int ReferenceBoxFilterKernel; + internal static int ReferenceBoxFilterBlueChannelKernel; + internal static int StandardErrorKernel; + internal static int StandardErrorThresholdKernel; + internal static int GetValueKernel; + internal static int NormalizeByAlphaKernel; + + public void Load() + { +#if UNITY_EDITOR + const string packageFolder = "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/"; + ComputeHelperShader = UnityEditor.AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/ComputeHelpers.compute"); +#endif + if (ComputeHelperShader != null) + { + MultiplyKernel = ComputeHelperShader.FindKernel("Multiply"); + BroadcastChannelKernel = ComputeHelperShader.FindKernel("BroadcastChannel"); + SetChannelKernel = ComputeHelperShader.FindKernel("SetChannel"); + ReferenceBoxFilterKernel = ComputeHelperShader.FindKernel("ReferenceBoxFilter"); + ReferenceBoxFilterBlueChannelKernel = ComputeHelperShader.FindKernel("ReferenceBoxFilterBlueChannel"); + StandardErrorKernel = ComputeHelperShader.FindKernel("StandardError"); + StandardErrorThresholdKernel = ComputeHelperShader.FindKernel("StandardErrorThreshold"); + GetValueKernel = ComputeHelperShader.FindKernel("GetValue"); + NormalizeByAlphaKernel = ComputeHelperShader.FindKernel("NormalizeByAlpha"); + } + } + } + + public class GPUSync : IDisposable + { + private RenderTexture _syncTexture; + private Texture2D _readableTex; + + public void Create() + { + Debug.Assert(_syncTexture == null); + _syncTexture = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32) + { + name = "_syncTexture", + enableRandomWrite = true, + hideFlags = HideFlags.HideAndDontSave + }; + _syncTexture.Create(); + _readableTex = new Texture2D(_syncTexture.width, _syncTexture.height, TextureFormat.ARGB32, false, true) + { + name = "_readableTex", + hideFlags = HideFlags.HideAndDontSave + }; + } + + public void Sync(CommandBuffer cmd) + { + Debug.Assert(_syncTexture != null, "Create must be called before Sync."); + Debug.Assert(_readableTex != null, "Create must be called before Sync."); + cmd.SetRenderTarget(_syncTexture); + cmd.ClearRenderTarget(false, true, new Color32(1, 2, 3, 4)); + cmd.RequestAsyncReadback(_syncTexture, delegate { }); + cmd.WaitAllAsyncReadbackRequests(); + GraphicsHelpers.Flush(cmd); + } + + public void RequestAsyncReadback(CommandBuffer cmd, Action callback) => + cmd.RequestAsyncReadback(_syncTexture, callback); + + public void Dispose() + { + if (_syncTexture != null) + { + _syncTexture.Release(); + CoreUtils.Destroy(_syncTexture); + _syncTexture = null; + } + + if (_readableTex == null) + return; + + CoreUtils.Destroy(_readableTex); + _readableTex = null; + } + } + + private static string IntegratedOutputTypeToComponentName(IntegratedOutputType integratedOutputType) + { + switch (integratedOutputType) + { + case IntegratedOutputType.Direct: return "irradianceDirect"; + case IntegratedOutputType.Indirect: return "irradianceIndirect"; + case IntegratedOutputType.AO: return "ambientOcclusion"; + case IntegratedOutputType.Validity: return "validity"; + case IntegratedOutputType.DirectionalityDirect: return "directionalityDirect"; + case IntegratedOutputType.DirectionalityIndirect: return "directionalityIndirect"; + case IntegratedOutputType.ShadowMask: return "shadowmask"; + default: + { + Debug.Assert(false, "Missing lightmap type in LightmapTypeToComponentName"); + return "Missing lightmap type in LightmapTypeToComponentName"; + } + } + } + + static string BuildLightmapComponentPath(string outputType, int lightmapIndex, string path) + { + // Naming convention is path/componentName+LightmapIndex.r2d + return $"{path}/{outputType}{lightmapIndex}.r2d"; + } + + public static bool WriteLightmap(CommandBuffer cmd, RenderTexture renderTex, string outputType, int lightmapIndex, string path) + { + try + { + DirectoryInfo directoryInfo = Directory.CreateDirectory(path); + if (directoryInfo.Exists == false) + return false; + + string fullPath = BuildLightmapComponentPath(outputType, lightmapIndex, path); + LightmapIntegrationHelpers.WriteRenderTexture(cmd, fullPath, renderTex); + return true; + } + catch (Exception e) + { + Debug.Assert(false, e.Message); + return false; + } + } + + public static bool WriteLightmap(CommandBuffer cmd, RenderTexture renderTex, IntegratedOutputType integratedOutputType, int lightmapIndex, string path) + { + return WriteLightmap(cmd, renderTex, IntegratedOutputTypeToComponentName(integratedOutputType), + lightmapIndex, path); + } + + private static void OutputUIntRequestData(string prefix, AsyncGPUReadbackRequest request) + { + string output = new(""); + Debug.Assert(!request.hasError); + if (!request.hasError) + { + var src = request.GetData(); + output = $"{prefix}:\n"; + for (int i = 0; i < src.Length; ++i) + { + output += src[i]; + if (i < src.Length - 1) + output += "\n"; + } + } + else + output = "AsyncReadBack failed"; + System.Console.WriteLine(output); + } + + private static void OutputFloat2RequestData(string prefix, AsyncGPUReadbackRequest request) + { + string output = new(""); + Debug.Assert(!request.hasError); + if (!request.hasError) + { + var src = request.GetData(); + Debug.Assert(src.Length % 2 == 0); + output = $"{prefix}:\n"; + for (int i = 0; i < src.Length; i += 2) + { + output += string.Format(System.Globalization.CultureInfo.InvariantCulture, "float2({0}, {1})", src[i], src[i + 1]); + if (i < src.Length - 1) + output += "\n"; + } + } + else + output = "AsyncReadBack failed"; + System.Console.WriteLine(output); + } + + private static void OutputFloat4RequestData(string prefix, AsyncGPUReadbackRequest request) + { + string output = new(""); + Debug.Assert(!request.hasError); + if (!request.hasError) + { + var src = request.GetData(); + Debug.Assert(src.Length % 4 == 0); + output = $"{prefix}:\n"; + for (int i = 0; i < src.Length; i+=4) + { + output += string.Format(System.Globalization.CultureInfo.InvariantCulture, "float4({0}, {1}, {2}, {3})", src[i], src[i + 1], src[i + 2], src[i + 3]); + if (i < src.Length - 1) + output += "\n"; + } + } + else + output = "AsyncReadBack failed"; + System.Console.WriteLine(output); + } + + public struct HitEntry + { + public uint instanceID; + public uint primitiveIndex; + public Unity.Mathematics.float2 barycentrics; + }; + + private static void OutputHitEntryRequestData(string prefix, AsyncGPUReadbackRequest request) + { + string output = new(""); + Debug.Assert(!request.hasError); + if (!request.hasError) + { + var src = request.GetData(); + output = $"{prefix}:\n"; + for (int i = 0; i < src.Length; i++) + { + output += $"hitEntry({src[i].instanceID}, {src[i].primitiveIndex}, bary: [{src[i].barycentrics.x}, {src[i].barycentrics.y}])"; + if (i < src.Length - 1) + output += "\n"; + } + } + else + output = "AsyncReadBack failed"; + System.Console.WriteLine(output); + } + + public enum LogBufferType + { + UInt, + Float2, + Float4, + HitEntry + } + + public static void LogGraphicsBuffer(CommandBuffer cmd, GraphicsBuffer graphicsBuffer, string prefix, LogBufferType type) + { + using GraphicsBuffer stagingBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopyDestination, graphicsBuffer.count, graphicsBuffer.stride); + cmd.CopyBuffer(graphicsBuffer, stagingBuffer); + switch (type) + { + case LogBufferType.UInt: + Debug.Assert(graphicsBuffer.stride == 4); + cmd.RequestAsyncReadback(stagingBuffer, (AsyncGPUReadbackRequest request) => { OutputUIntRequestData(prefix, request); }); + break; + case LogBufferType.Float2: + Debug.Assert(graphicsBuffer.stride == 8); + cmd.RequestAsyncReadback(stagingBuffer, (AsyncGPUReadbackRequest request) => { OutputFloat2RequestData(prefix, request); }); + break; + case LogBufferType.Float4: + Debug.Assert(graphicsBuffer.stride == 16); + cmd.RequestAsyncReadback(stagingBuffer, (AsyncGPUReadbackRequest request) => { OutputFloat4RequestData(prefix, request); }); + break; + case LogBufferType.HitEntry: + Debug.Assert(graphicsBuffer.stride == 16); + cmd.RequestAsyncReadback(stagingBuffer, (AsyncGPUReadbackRequest request) => { OutputHitEntryRequestData(prefix, request); }); + break; + default: + Debug.LogWarning($"LogGraphicsBuffer: GraphicsBuffer type {type} is not implemented."); + break; + } + cmd.WaitAllAsyncReadbackRequests(); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Clear(); + } + + public static bool IsPow2(int value) => (value & (value - 1)) == 0 && value > 0; + + internal static GraphicsBuffer CreateDispatchDimensionBuffer() + { + return new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, 3, sizeof(uint)); + } + + private static void ReadbackTexture(Texture2D texture, AsyncGPUReadbackRequest request) + { + if (request.hasError) + { + Debug.Assert(request.hasError, "GPU readback request error detected."); + return; + } + if (request.done) + { + var data = request.GetData(); + texture.LoadRawTextureData(data); + return; + } + Debug.Assert(false, "GPU readback request not done."); + } + + internal static byte[] EncodeToR2D(this Texture2D tex) // EncodeToR2DInternal is internal to the Editor, this enables us to call it from here + { + Type encoder = typeof(ImageConversion); + + MethodInfo encodeMethod = encoder.GetMethod("EncodeToR2DInternal", BindingFlags.Static | BindingFlags.NonPublic); + if (encodeMethod == null) + return Array.Empty(); + + var encodeFunc = (Func)Delegate.CreateDelegate(typeof(Func), encodeMethod); + + return encodeFunc(tex); + } + + internal static void WriteRenderTexture(CommandBuffer cmd, RenderTargetIdentifier renderTex, TextureFormat textureFormat, int width, int height, string path) + { + Texture2D readableTex = new Texture2D(width, height, textureFormat, false) { name = "readableTex (WriteRenderTexture)", hideFlags = HideFlags.HideAndDontSave}; + cmd.CopyTexture(renderTex, readableTex); + cmd.RequestAsyncReadback(readableTex, 0, (AsyncGPUReadbackRequest request) => { ReadbackTexture(readableTex, request); }); + cmd.WaitAllAsyncReadbackRequests(); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Clear(); + + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(path))) + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + System.IO.File.WriteAllBytes(path, readableTex.EncodeToR2D()); + CoreUtils.Destroy(readableTex); + } + + internal static void WriteRenderTexture(CommandBuffer cmd, string path, RenderTexture renderTex) + { + Debug.Assert(renderTex.format == RenderTextureFormat.ARGBFloat); + WriteRenderTexture(cmd, renderTex, TextureFormat.RGBAFloat, renderTex.width, renderTex.height, path); + } + + public static double4 GetSum(int width, int height, RenderTexture renderTex) + { + RenderTexture previous = RenderTexture.active; + RenderTexture.active = renderTex; + Texture2D readableTex = new Texture2D(width, height, TextureFormat.RGBAFloat, false, true) { name = "GetSum.readableTex", hideFlags = HideFlags.HideAndDontSave }; + Assert.IsTrue(readableTex.isReadable); + readableTex.ReadPixels(new Rect(0, 0, width, height), 0, 0); + RenderTexture.active = previous; + Color[] colors = readableTex.GetPixels(); + double4 colorSum = new double4(0.0, 0.0, 0.0, 0.0); + foreach (var color in colors) + colorSum += new double4(color.r, color.g, color.b, color.a); + CoreUtils.Destroy(readableTex); + return colorSum; + } + + public static float Luminance(Color color) + { + return color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f; + } + + public static Color GetValue(CommandBuffer cmd, ComputeShader computeShader, int getValueKernel, int sampleX, int sampleY, int width, int height, RenderTargetIdentifier renderTargetIdentifier) + { + using ComputeBuffer colorBuffer = new(4, sizeof(float)); + cmd.SetComputeBufferParam(computeShader, getValueKernel, ComputeHelpers.ShaderIDs.OutputBuffer, colorBuffer); + cmd.SetComputeTextureParam(computeShader, getValueKernel, ComputeHelpers.ShaderIDs.SourceTexture, renderTargetIdentifier); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.X, sampleX); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.Y, sampleY); + computeShader.GetKernelThreadGroupSizes(getValueKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(computeShader, getValueKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Clear(); + float[] tempColorArray = new float[4]; + colorBuffer.GetData(tempColorArray); + colorBuffer.Release(); + return new Color(tempColorArray[0], tempColorArray[1], tempColorArray[2], tempColorArray[3]); + } + + public static void NormalizeByAlpha(CommandBuffer cmd, ComputeShader computeShader, int normalizeByAlphaKernel, int width, int height, RenderTargetIdentifier inOut) + { + cmd.SetComputeTextureParam(computeShader, normalizeByAlphaKernel, ComputeHelpers.ShaderIDs.TextureInOut, inOut); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(computeShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + computeShader.GetKernelThreadGroupSizes(normalizeByAlphaKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(computeShader, normalizeByAlphaKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void MultiplyRenderTexture(CommandBuffer cmd, ComputeShader multiplyShader, int multiplyKernel, RenderTargetIdentifier inOut, int width, int height, Vector4 multiplicationFactor) + { + cmd.SetComputeTextureParam(multiplyShader, multiplyKernel, ComputeHelpers.ShaderIDs.TextureInOut, inOut); + cmd.SetComputeIntParam(multiplyShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(multiplyShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeVectorParam(multiplyShader, ComputeHelpers.ShaderIDs.MultiplicationFactor, multiplicationFactor); + multiplyShader.GetKernelThreadGroupSizes(multiplyKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(multiplyShader, multiplyKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void MultiplyTexture(CommandBuffer cmd, ComputeShader multiplyShader, int multiplyKernel, Texture2D texture, Vector4 multiplicationFactor) + { + int renderTargetID = ComputeHelpers.ShaderIDs.MultiplyTemporaryRenderTarget; + cmd.GetTemporaryRT(renderTargetID, texture.width, texture.height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + cmd.CopyTexture(texture, renderTargetID); + LightmapIntegrationHelpers.MultiplyRenderTexture(cmd, multiplyShader, multiplyKernel, renderTargetID, texture.width, texture.height, multiplicationFactor); + cmd.CopyTexture(renderTargetID, texture); + cmd.ReleaseTemporaryRT(renderTargetID); + } + + public static void StandardErrorRenderTexture(CommandBuffer cmd, ComputeShader standardError, int standardErrorKernel, RenderTargetIdentifier varianceInR, RenderTargetIdentifier sampleCountInW, RenderTargetIdentifier output, int width, int height) + { + cmd.SetComputeTextureParam(standardError, standardErrorKernel, ComputeHelpers.ShaderIDs.VarianceInR, varianceInR); + cmd.SetComputeTextureParam(standardError, standardErrorKernel, ComputeHelpers.ShaderIDs.SampleCountInW, sampleCountInW); + cmd.SetComputeTextureParam(standardError, standardErrorKernel, ComputeHelpers.ShaderIDs.TextureOut, output); + cmd.SetComputeIntParam(standardError, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(standardError, ComputeHelpers.ShaderIDs.TextureHeight, height); + standardError.GetKernelThreadGroupSizes(standardErrorKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(standardError, standardErrorKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void StandardErrorThresholdRenderTexture(CommandBuffer cmd, ComputeShader standardErrorThreshold, int StandardErrorThresholdKernel, RenderTargetIdentifier standardErrorInR, RenderTargetIdentifier meanInR, float standardErrorThresholdValue, RenderTargetIdentifier output, int width, int height) + { + cmd.SetComputeTextureParam(standardErrorThreshold, StandardErrorThresholdKernel, ComputeHelpers.ShaderIDs.StandardErrorInR, standardErrorInR); + cmd.SetComputeTextureParam(standardErrorThreshold, StandardErrorThresholdKernel, ComputeHelpers.ShaderIDs.MeanInR, meanInR); + cmd.SetComputeTextureParam(standardErrorThreshold, StandardErrorThresholdKernel, ComputeHelpers.ShaderIDs.TextureOut, output); + cmd.SetComputeFloatParam(standardErrorThreshold, ComputeHelpers.ShaderIDs.StandardErrorThreshold, standardErrorThresholdValue); + cmd.SetComputeIntParam(standardErrorThreshold, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(standardErrorThreshold, ComputeHelpers.ShaderIDs.TextureHeight, height); + standardErrorThreshold.GetKernelThreadGroupSizes(StandardErrorThresholdKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(standardErrorThreshold, StandardErrorThresholdKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void BroadcastChannelRenderTexture(CommandBuffer cmd, ComputeShader broadcastChannelShader, int broadcastChannelKernel, RenderTargetIdentifier inOut, int width, int height, int channelIndex) + { + cmd.SetComputeTextureParam(broadcastChannelShader, broadcastChannelKernel, ComputeHelpers.ShaderIDs.TextureInOut, inOut); + cmd.SetComputeIntParam(broadcastChannelShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(broadcastChannelShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeIntParam(broadcastChannelShader, ComputeHelpers.ShaderIDs.ChannelIndex, channelIndex); + broadcastChannelShader.GetKernelThreadGroupSizes(broadcastChannelKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(broadcastChannelShader, broadcastChannelKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void SetChannelRenderTexture(CommandBuffer cmd, ComputeShader setChannelShader, int setChannelKernel, RenderTargetIdentifier inOut, int width, int height, int channelIndex, float channelValue) + { + cmd.SetComputeTextureParam(setChannelShader, setChannelKernel, ComputeHelpers.ShaderIDs.TextureInOut, inOut); + cmd.SetComputeIntParam(setChannelShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(setChannelShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeIntParam(setChannelShader, ComputeHelpers.ShaderIDs.ChannelIndex, channelIndex); + cmd.SetComputeFloatParam(setChannelShader, ComputeHelpers.ShaderIDs.ChannelValue, channelValue); + setChannelShader.GetKernelThreadGroupSizes(setChannelKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(setChannelShader, setChannelKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + } + + public static void ReferenceBoxFilterRenderTexture(CommandBuffer cmd, ComputeShader referenceBoxFilterShader, int referenceBoxFilterKernel, RenderTargetIdentifier inOut, int width, int height, int radius) + { + cmd.GetTemporaryRT(ComputeHelpers.ShaderIDs.TextureOut, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + cmd.SetComputeTextureParam(referenceBoxFilterShader, referenceBoxFilterKernel, ComputeHelpers.ShaderIDs.SourceTexture, inOut); + cmd.SetComputeTextureParam(referenceBoxFilterShader, referenceBoxFilterKernel, ComputeHelpers.ShaderIDs.TextureOut, ComputeHelpers.ShaderIDs.TextureOut); + cmd.SetComputeIntParam(referenceBoxFilterShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(referenceBoxFilterShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeIntParam(referenceBoxFilterShader, ComputeHelpers.ShaderIDs.BoxFilterRadius, radius); + referenceBoxFilterShader.GetKernelThreadGroupSizes(referenceBoxFilterKernel, out uint x, out uint y, out _); + cmd.DispatchCompute(referenceBoxFilterShader, referenceBoxFilterKernel, GraphicsHelpers.DivUp(width, x), GraphicsHelpers.DivUp(height, y), 1); + cmd.CopyTexture(ComputeHelpers.ShaderIDs.TextureOut, inOut); + cmd.ReleaseTemporaryRT(ComputeHelpers.ShaderIDs.TextureOut); + } + + public static void ReferenceBoxFilterBlueChannelRenderTexture(CommandBuffer cmd, ComputeShader referenceBoxFilterBlueChannelShader, int referenceBoxFilterBlueChannelKernel, RenderTargetIdentifier inOut, int width, int height, int radius, GraphicsBuffer indirectDispatchBuffer) + { + cmd.BeginSample("BoxFiltering"); + cmd.GetTemporaryRT(ComputeHelpers.ShaderIDs.TextureOut, width, height, 0, FilterMode.Point, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear, 1, true); + cmd.SetComputeTextureParam(referenceBoxFilterBlueChannelShader, referenceBoxFilterBlueChannelKernel, ComputeHelpers.ShaderIDs.SourceTexture, inOut); + cmd.SetComputeTextureParam(referenceBoxFilterBlueChannelShader, referenceBoxFilterBlueChannelKernel, ComputeHelpers.ShaderIDs.TextureOut, ComputeHelpers.ShaderIDs.TextureOut); + cmd.SetComputeIntParam(referenceBoxFilterBlueChannelShader, ComputeHelpers.ShaderIDs.TextureWidth, width); + cmd.SetComputeIntParam(referenceBoxFilterBlueChannelShader, ComputeHelpers.ShaderIDs.TextureHeight, height); + cmd.SetComputeIntParam(referenceBoxFilterBlueChannelShader, ComputeHelpers.ShaderIDs.BoxFilterRadius, radius); + cmd.DispatchCompute(referenceBoxFilterBlueChannelShader, referenceBoxFilterBlueChannelKernel, indirectDispatchBuffer, 0); + cmd.CopyTexture(ComputeHelpers.ShaderIDs.TextureOut, inOut); // TODO(jesper): dont do copy + cmd.ReleaseTemporaryRT(ComputeHelpers.ShaderIDs.TextureOut); + cmd.EndSample("BoxFiltering"); + } + + // Computes the region of a lightmap that is occupied by the pixels of a specific instance, + // given the lightmap size, the instance's scale and offset within the lightmap (lightmap ST), + // and the bounding box of the instance's UVs. + // + // NOTE: Only considering the region defined by the instance's lightmap ST causes issues, because + // these regions can overlap for multiple instances, when the instance's UV layout doesn't fully + // cover the [0; 1] range. We pack based on tight bounding boxes of the instance's UVs, + // but lightmap STs are with respect to the instance's entire UV layout. + // It is assumed that the lightmap offset is aligned to either half or full texel. + internal static void ComputeOccupiedTexelRegionForInstance( + uint lightmapWidth, + uint lightmapHeight, + Vector4 instanceLightmapST, + Vector2 uvBoundsSize, + Vector2 uvBoundsOffset, + out Vector4 normalizedOccupiedST, + out Vector2Int occupiedTexelSize, + out Vector2Int occupiedTexelOffset) + { + // Transform the "instance stamp" bounds to contain only the occupied region. + float normalizedOccupiedWidth = instanceLightmapST.x * uvBoundsSize.x; + float normalizedOccupiedHeight = instanceLightmapST.y * uvBoundsSize.y; + + float normalizedOccupiedOffsetX = instanceLightmapST.z + uvBoundsOffset.x * instanceLightmapST.x; + float normalizedOccupiedOffsetY = instanceLightmapST.w + uvBoundsOffset.y * instanceLightmapST.y; + + normalizedOccupiedST = new Vector4(normalizedOccupiedWidth, normalizedOccupiedHeight, normalizedOccupiedOffsetX, normalizedOccupiedOffsetY); + + // Transform the occupied region to lightmap pixel coordinate space. + float occupiedWidth = lightmapWidth * normalizedOccupiedWidth; + float occupiedHeight = lightmapHeight * normalizedOccupiedHeight; + + float occupiedOffsetX = lightmapWidth * normalizedOccupiedOffsetX; + float occupiedOffsetY = lightmapHeight * normalizedOccupiedOffsetY; + + // Round up to the nearest integer to ensure we cover all texels that the instance's UVs touch. + occupiedTexelSize = new(Mathf.CeilToInt(occupiedWidth), Mathf.CeilToInt(occupiedHeight)); + + // Clamp the occupied region to the lightmap bounds. + float occupiedTexelOffsetXFloat = Mathf.Max(occupiedOffsetX, 0f); + float occupiedTexelOffsetYFloat = Mathf.Max(occupiedOffsetY, 0f); + + // Get the fractional value + float offsetFracX = Mathf.Abs(occupiedTexelOffsetXFloat - Mathf.Floor(occupiedTexelOffsetXFloat)); + float offsetFracY = Mathf.Abs(occupiedTexelOffsetYFloat - Mathf.Floor(occupiedTexelOffsetYFloat)); + + // Check that we are either aligned to half or full texel + float distanceToHalfX = Mathf.Abs(offsetFracX - 0.5f); + float distanceToHalfY = Mathf.Abs(offsetFracY - 0.5f); + float distanceToWholeX = Mathf.Abs(Mathf.Round(offsetFracX) - offsetFracX); + float distanceToWholeY = Mathf.Abs(Mathf.Round(offsetFracY) - offsetFracY); + + Debug.Assert(Mathf.Min(distanceToHalfX, distanceToWholeX) < 0.001f, $"Expected offset (X) to align with an offset of 0.5 (half texel) or 0.0 (whole texel). Was {occupiedTexelOffsetXFloat}. Was the scene baked with the same resolution as it was atlassed for?"); + Debug.Assert(Mathf.Min(distanceToHalfY, distanceToWholeY) < 0.001f, $"Expected offset (Y) to align with an offset of 0.5 (half texel) or 0.0 (whole texel). Was {occupiedTexelOffsetYFloat}. Was the scene baked with the same resolution as it was atlassed for?"); + Debug.Assert((distanceToHalfX < distanceToWholeX && distanceToHalfY < distanceToWholeY) || (distanceToHalfX > distanceToWholeX && distanceToHalfY > distanceToWholeY), "Texel alignment with an offset of 0.5 (half texel) or 0.0 (whole texel) must be the same for X and Y"); + + int occupiedTexelOffsetX = 0; + int occupiedTexelOffsetY = 0; + + if (distanceToHalfX < distanceToWholeX) + occupiedTexelOffsetX = (int)Mathf.Floor(occupiedTexelOffsetXFloat); + else + occupiedTexelOffsetX = (int)Mathf.Round(occupiedTexelOffsetXFloat); + + if (distanceToHalfY < distanceToWholeY) + occupiedTexelOffsetY = (int)Mathf.Floor(occupiedTexelOffsetYFloat); + else + occupiedTexelOffsetY = (int)Mathf.Round(occupiedTexelOffsetYFloat); + + occupiedTexelOffset = new(occupiedTexelOffsetX, occupiedTexelOffsetY); + } + + // Computes the bounding box of a set of UV coordinates. + internal static void ComputeUVBounds(IEnumerable uvs, out Vector2 size, out Vector2 offset) + { + Vector2 minUV = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxUV = new Vector2(float.MinValue, float.MinValue); + foreach (var uv in uvs) + { + minUV = Vector2.Min(minUV, uv); + maxUV = Vector2.Max(maxUV, uv); + } + size = new Vector2(maxUV.x - minUV.x, maxUV.y - minUV.y); + offset = minUV; + } + + public class AdaptiveSample + { + public uint sampleCount; + public float accumulatedLuminance; + public float mean; + public float meanSqr; + public float variance; + public float varianceFiltered; + public float standardError; + public bool active; + override public string ToString() + { + string samplesString = new(""); + samplesString += sampleCount.ToString() + "\t"; + samplesString += accumulatedLuminance.ToString("F99").TrimEnd('0') + "\t"; + samplesString += mean.ToString("F99").TrimEnd('0') + "\t"; + samplesString += meanSqr.ToString("F99").TrimEnd('0') + "\t"; + samplesString += variance.ToString("F99").TrimEnd('0') + "\t"; + samplesString += varianceFiltered.ToString("F99").TrimEnd('0') + "\t"; + samplesString += standardError.ToString("F99").TrimEnd('0') + "\t"; + samplesString += active.ToString() + "\n"; + return samplesString; + } + public static string HeaderString() + { + string headerString = new(""); + headerString += "sampleCount\taccumulatedLuminance\tmean\tmeanSqr\tvariance\tvarianceFiltered\tstandardError\tactive\n"; + return headerString; + } + + static public string SamplesToString(AdaptiveSample[] samples, int x, int y, float adaptiveThreshold) + { + if (samples.Length == 0) + return ""; + string samplesString = new($"data for pixel [{x}, {y}], threshold:\t{adaptiveThreshold}\n"); + samplesString += AdaptiveSample.HeaderString(); + foreach (AdaptiveSample sample in samples) + { + samplesString += sample.ToString(); + } + return samplesString; + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs.meta new file mode 100644 index 00000000000..5b8fbcd47ae --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapIntegrationHelpers.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e9dd43ea62c0dd43be9269bb4e53a11 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs new file mode 100644 index 00000000000..0e7d4006c09 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal class LightmapIntegrationResourceCache : IDisposable + { + private Dictionary _meshToUVMesh = new(); + private Dictionary _meshToUVAccelerationStructure = new(); + private Dictionary _meshToUVFallbackBuffer = new(); + + private struct UVFallbackBufferKey : IEquatable + { + int width; + int height; + ulong meshInstanceID; + + public UVFallbackBufferKey(int width, int height, EntityId meshInstanceID) + { + this.width = width; + this.height = height; + this.meshInstanceID = Util.EntityIDToUlong(meshInstanceID); + } + + public bool Equals(UVFallbackBufferKey other) => + width == other.width && height == other.height && meshInstanceID == other.meshInstanceID; + public override bool Equals(object obj) => obj is UVFallbackBufferKey other && Equals(other); + public override int GetHashCode() => HashCode.Combine(width, height, meshInstanceID); + public static bool operator ==(UVFallbackBufferKey left, UVFallbackBufferKey right) => left.Equals(right); + public static bool operator !=(UVFallbackBufferKey left, UVFallbackBufferKey right) => !left.Equals(right); + } + + public int UVMeshCount() + { + return _meshToUVMesh.Count; + } + + public int UVAccelerationStructureCount() + { + return _meshToUVAccelerationStructure.Count; + } + + public int UVFallbackBufferCount() + { + return _meshToUVFallbackBuffer.Count; + } + + internal bool CacheIsHot(BakeInstance[] instances) + { + foreach (BakeInstance instance in instances) + { + // UVMesh + ulong uvMeshHash = Util.EntityIDToUlong(instance.Mesh.GetEntityId()); + if (!_meshToUVMesh.TryGetValue(uvMeshHash, out UVMesh uvMesh)) + return false; + + // UVAccelerationStructure + ulong uvASHash = uvMeshHash; + if (!_meshToUVAccelerationStructure.TryGetValue(uvASHash, out UVAccelerationStructure uvAS)) + return false; + + // UVFallbackBuffer + var uvFBKey = new UVFallbackBufferKey(instance.TexelSize.x, instance.TexelSize.y, uvMesh.Mesh.GetEntityId()); + if (!_meshToUVFallbackBuffer.TryGetValue(uvFBKey, out UVFallbackBuffer uvFB)) + return false; + } + return true; + } + + internal bool AddResources( + BakeInstance[] instances, + RayTracingContext context, + CommandBuffer cmd, + UVFallbackBufferBuilder uvFallbackBufferBuilder) + { + foreach (BakeInstance instance in instances) + { + // UVMesh + ulong uvMeshHash = Util.EntityIDToUlong(instance.Mesh.GetEntityId()); + if (!_meshToUVMesh.TryGetValue(uvMeshHash, out UVMesh uvMesh)) + { + UVMesh newUVMesh = new(); + if (!newUVMesh.Build(instance.Mesh)) + { + newUVMesh?.Dispose(); + return false; + } + _meshToUVMesh.Add(uvMeshHash, newUVMesh); + uvMesh = newUVMesh; + } + // UVAccelerationStructure + ulong uvASHash = Util.EntityIDToUlong(uvMesh.Mesh.GetEntityId()); + if (!_meshToUVAccelerationStructure.TryGetValue(uvASHash, out UVAccelerationStructure uvAS)) + { + cmd.BeginSample("Build UVAccelerationStructure"); + UVAccelerationStructure newUVAS = new(); + newUVAS.Build(cmd, context, uvMesh, BuildFlags.None); + _meshToUVAccelerationStructure.Add(uvASHash, newUVAS); + uvAS = newUVAS; + cmd.EndSample("Build UVAccelerationStructure"); + } + // UVFallbackBuffer + var uvFBKey = new UVFallbackBufferKey(instance.TexelSize.x, instance.TexelSize.y, uvMesh.Mesh.GetEntityId()); + if (!_meshToUVFallbackBuffer.TryGetValue(uvFBKey, out UVFallbackBuffer uvFB)) + { + UVFallbackBuffer newUVFB = new(); + if (!newUVFB.Build( + cmd, + uvFallbackBufferBuilder, + instance.TexelSize.x, + instance.TexelSize.y, + uvMesh)) + { + newUVFB?.Dispose(); + return false; + } + _meshToUVFallbackBuffer.Add(uvFBKey, newUVFB); + uvFB = newUVFB; + } + } + return true; + } + + internal void FreeResources(BakeInstance[] instancesToKeep) + { + // Build dictionary over resources to keep + Dictionary uvMeshesToKeep = new(); + Dictionary uvASToKeep = new(); + Dictionary uvFBToKeep = new(); + foreach (var instance in instancesToKeep) + { + ulong uvMeshHash = Util.EntityIDToUlong(instance.Mesh.GetEntityId()); + if (_meshToUVMesh.TryGetValue(uvMeshHash, out UVMesh uvMesh)) + { + uvMeshesToKeep.Add(uvMeshHash, uvMesh); + _meshToUVMesh.Remove(uvMeshHash); + ulong uvASHash = Util.EntityIDToUlong(uvMesh.Mesh.GetEntityId()); + if (_meshToUVAccelerationStructure.TryGetValue(uvASHash, out UVAccelerationStructure uvAS)) + { + uvASToKeep.Add(uvASHash, uvAS); + _meshToUVAccelerationStructure.Remove(uvASHash); + var uvFBKey = new UVFallbackBufferKey(instance.TexelSize.x, instance.TexelSize.y, uvMesh.Mesh.GetEntityId()); + if (_meshToUVFallbackBuffer.TryGetValue(uvFBKey, out UVFallbackBuffer uvFB)) + { + uvFBToKeep.Add(uvFBKey, uvFB); + _meshToUVFallbackBuffer.Remove(uvFBKey); + } + } + } + } + + // Dispose remaining resources + Clear(); + + // Restore resources to keep + _meshToUVMesh = uvMeshesToKeep; + _meshToUVAccelerationStructure = uvASToKeep; + _meshToUVFallbackBuffer = uvFBToKeep; + } + + internal bool GetResources( + BakeInstance[] instances, + out UVMesh[] uvMeshes, + out UVAccelerationStructure[] uvAccelerationStructures, + out UVFallbackBuffer[] uvFallbackBuffers) + { + List uvMeshList = new(); + List uvAccelerationStructureList = new(); + List uvFallbackBufferList = new(); + uvMeshes = null; + uvAccelerationStructures = null; + uvFallbackBuffers = null; + foreach (var instance in instances) + { + ulong uvMeshHash = Util.EntityIDToUlong(instance.Mesh.GetEntityId()); + if (!_meshToUVMesh.TryGetValue(uvMeshHash, out UVMesh uvMesh)) + return false; + uvMeshList.Add(uvMesh); + ulong uvASHash = Util.EntityIDToUlong(uvMesh.Mesh.GetEntityId()); + if (!_meshToUVAccelerationStructure.TryGetValue(uvASHash, out UVAccelerationStructure uvAS)) + return false; + uvAccelerationStructureList.Add(uvAS); + var uvFBKey = new UVFallbackBufferKey(instance.TexelSize.x, instance.TexelSize.y, uvMesh.Mesh.GetEntityId()); + if (!_meshToUVFallbackBuffer.TryGetValue(uvFBKey, out UVFallbackBuffer uvFB)) + return false; + uvFallbackBufferList.Add(uvFB); + } + uvMeshes = uvMeshList.ToArray(); + uvAccelerationStructures = uvAccelerationStructureList.ToArray(); + uvFallbackBuffers = uvFallbackBufferList.ToArray(); + return true; + } + + private void Clear() + { + foreach (var uvMesh in _meshToUVMesh) + uvMesh.Value.Dispose(); + foreach (var uvAccelerationStructure in _meshToUVAccelerationStructure) + uvAccelerationStructure.Value.Dispose(); + foreach (var uvFallbackBuffer in _meshToUVFallbackBuffer) + uvFallbackBuffer.Value.Dispose(); + _meshToUVMesh.Clear(); + _meshToUVAccelerationStructure.Clear(); + _meshToUVFallbackBuffer.Clear(); + } + + public void Dispose() + { + Clear(); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs.meta new file mode 100644 index 00000000000..e1056e11c2d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmapResourceCache.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 54b4c219a2fd01e439bdc9ef8bc2faf8 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping.meta new file mode 100644 index 00000000000..a8ea15ea38e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b049d4faa1758345969290b189e9c0b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs new file mode 100644 index 00000000000..3654d0e523f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using Unity.Jobs; +using Unity.Collections; +using Unity.Mathematics; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal static class ChartIdentification + { + private static UInt32 FindRepWithPathCompression(Span reps, UInt32 vertexIdx) + { + UInt32 rep = reps[(int)vertexIdx]; + if (rep == vertexIdx) + return vertexIdx; + + rep = FindRepWithPathCompression(reps, rep); + reps[(int)vertexIdx] = rep; + return rep; + } + + private static UInt32 FindRepresentative(Span reps, UInt32 vertexIdx) + { + UInt32 rep = reps[(int)vertexIdx]; + return (rep == vertexIdx ? vertexIdx : FindRepresentative(reps, rep)); + } + + private static void Union(Span reps, UInt32 vertexIdx0, UInt32 vertexIdx1) + { + var rep0 = FindRepWithPathCompression(reps, vertexIdx0); + var rep1 = FindRepWithPathCompression(reps, vertexIdx1); + reps[(int)rep1] = rep0; + } + + public static void UnionTriangleEdges(ReadOnlySpan triangleIndices, Span vertexChartIds) + { + Span reps = vertexChartIds; + + Debug.Assert(triangleIndices.Length % 3 == 0); + + int triangleCount = triangleIndices.Length / 3; + + for (int triIdx = 0; triIdx < triangleCount; ++triIdx) + { + int offset = triIdx * 3; + UInt32 vertexIdx0 = triangleIndices[offset]; + UInt32 vertexIdx1 = triangleIndices[offset + 1]; + UInt32 vertexIdx2 = triangleIndices[offset + 2]; + Union(reps, vertexIdx0, vertexIdx1); + Union(reps, vertexIdx1, vertexIdx2); + } + } + + public static void UnionDuplicateVertices(ReadOnlySpan vertexUvs, ReadOnlySpan vertexPositions, ReadOnlySpan vertexNormals, Span vertexChartIds, bool respectNormals) + { + var map = new Dictionary<(float2, float3, float3), uint>(); + for (uint i = 0; i < vertexChartIds.Length; ++i) + { + var tuple = ( + vertexUvs[(int)i], + vertexPositions[(int)i], + respectNormals ? vertexNormals[(int)i] : float3.zero); + if (map.TryGetValue(tuple, out uint deduplicatedIndex)) + { + Union(vertexChartIds, i, deduplicatedIndex); + } + else + { + map.Add(tuple, i); + } + } + } + + public static void FindRepresentatives(Span vertexChartIds) + { + for (uint i = 0; i < vertexChartIds.Length; ++i) + { + vertexChartIds[(int)i] = FindRepresentative(vertexChartIds, i); + } + } + + public static void InitializeRepresentatives(Span vertexChartIds) + { + for (UInt32 i = 0; i < vertexChartIds.Length; ++i) + vertexChartIds[(int)i] = i; + } + + public static void Compact(Span vertexChartIds, out uint chartCount) + { + var map = new Dictionary(); + for (int vertexIdx = 0; vertexIdx < vertexChartIds.Length; ++vertexIdx) + { + var chartId = vertexChartIds[vertexIdx]; + UInt32 newChartId; + if (map.TryGetValue(chartId, out UInt32 compactedId)) + { + newChartId = compactedId; + } + else + { + newChartId = (UInt32)map.Count; + map.Add(chartId, newChartId); + } + vertexChartIds[vertexIdx] = newChartId; + } + chartCount = (uint)map.Count; + } + } + + internal class ParallelChartIdentification : IDisposable + { + private readonly MeshChartIdentificationJob[] _jobs; + private readonly JobHandle[] _jobHandles; + private readonly NativeArray[] _outputVertexChartIndices; + private readonly NativeArray[] _outputVertexChartIndicesIgnoringNormals; + private readonly NativeArray[] _outputChartCounts; + private readonly Dictionary _meshToJobIdx; + + public struct MeshResult + { + // Vertex -> Chart index mapping taking normals into account. + // Triangles are considered belonging to different charts if + // only connected by overlapping vertices which have different normals. + // Used for the 'baked UV charts' output, which is used to + // prevent filtering over hard edges during postprocessing. + public NativeArray VertexChartIndices; + public uint ChartCount; + + // Vertex -> Chart index mapping NOT taking normals into account. + // Triangles are considered belonging to the SAME chart if + // only connected by overlapping vertices which have different normals. + // Used for the 'baked UV overlap', since bilinear bleeding across hard edges + // within a single UV island is considered acceptable. + public NativeArray VertexChartIndicesIgnoringNormals; + public uint ChartCountIgnoringNormals; + }; + + private struct MeshChartIdentificationJob : IJob + { + [ReadOnly, DeallocateOnJobCompletion] + public NativeArray InputVertexIndexBuffer; + + [ReadOnly, DeallocateOnJobCompletion] + public NativeArray InputVertexUvBuffer; + + [ReadOnly, DeallocateOnJobCompletion] + public NativeArray InputVertexPositionBuffer; + + [ReadOnly, DeallocateOnJobCompletion] + public NativeArray InputVertexNormalBuffer; + + public NativeArray OutputVertexChartIndicesBuffer; + public NativeArray OutputVertexChartIndicesIgnoringNormalsBuffer; + + // Always 2 elements. Job system doesn't support scalar outputs. + public NativeArray OutputChartCount; + + public void Execute() + { + ChartIdentification.InitializeRepresentatives(OutputVertexChartIndicesBuffer); + ChartIdentification.UnionTriangleEdges(InputVertexIndexBuffer, OutputVertexChartIndicesBuffer); + + // Union duplicate verts taking normal into account + ChartIdentification.UnionDuplicateVertices(InputVertexUvBuffer, InputVertexPositionBuffer, InputVertexNormalBuffer, OutputVertexChartIndicesBuffer, respectNormals: true); + ChartIdentification.FindRepresentatives(OutputVertexChartIndicesBuffer); + + // Union duplicate verts NOT taking normal into account + OutputVertexChartIndicesBuffer.CopyTo(OutputVertexChartIndicesIgnoringNormalsBuffer); + ChartIdentification.UnionDuplicateVertices(InputVertexUvBuffer, InputVertexPositionBuffer, InputVertexNormalBuffer, OutputVertexChartIndicesIgnoringNormalsBuffer, respectNormals: false); + ChartIdentification.FindRepresentatives(OutputVertexChartIndicesIgnoringNormalsBuffer); + + // Compact and output both mappings + ChartIdentification.Compact(OutputVertexChartIndicesBuffer, out uint outputChartCount); + ChartIdentification.Compact(OutputVertexChartIndicesIgnoringNormalsBuffer, out uint outputChartIgnoringNormalsCount); + OutputChartCount[0] = outputChartCount; + OutputChartCount[1] = outputChartIgnoringNormalsCount; + } + } + + public ParallelChartIdentification(IList meshes) + { + _outputVertexChartIndices = new NativeArray[meshes.Count]; + _outputVertexChartIndicesIgnoringNormals = new NativeArray[meshes.Count]; + _outputChartCounts = new NativeArray[meshes.Count]; + _jobs = new MeshChartIdentificationJob[meshes.Count]; + _jobHandles = new JobHandle[meshes.Count]; + _meshToJobIdx = new Dictionary(); + + for (uint meshIdx = 0; meshIdx < meshes.Count; ++meshIdx) + { + var mesh = meshes[(int)meshIdx]; + + // Select uv buffer, prefer uv2 + var uvBuffer = mesh.uv2; + if (uvBuffer == null || uvBuffer.Length == 0) + uvBuffer = mesh.uv; + + var inputVertexIndices = new NativeArray(mesh.triangles, Allocator.TempJob).Reinterpret(sizeof(Int32)); + var inputVertexUvs = new NativeArray(uvBuffer, Allocator.TempJob).Reinterpret(sizeof(float) * 2); + var inputVertexPositions = new NativeArray(mesh.vertices, Allocator.TempJob).Reinterpret(sizeof(float) * 3); + var inputVertexNormals = new NativeArray(mesh.normals, Allocator.TempJob).Reinterpret(sizeof(float) * 3); + var outputChartIndices = new NativeArray(mesh.vertexCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var outputChartIndicesIgnoringNormals = new NativeArray(mesh.vertexCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var outputChartCount = new NativeArray(2, Allocator.TempJob); + var job = new MeshChartIdentificationJob + { + InputVertexIndexBuffer = inputVertexIndices, + InputVertexUvBuffer = inputVertexUvs, + InputVertexPositionBuffer = inputVertexPositions, + InputVertexNormalBuffer = inputVertexNormals, + OutputVertexChartIndicesBuffer = outputChartIndices, + OutputVertexChartIndicesIgnoringNormalsBuffer = outputChartIndicesIgnoringNormals, + OutputChartCount = outputChartCount, + }; + + _outputVertexChartIndices[meshIdx] = outputChartIndices; + _outputVertexChartIndicesIgnoringNormals[meshIdx] = outputChartIndicesIgnoringNormals; + _outputChartCounts[meshIdx] = outputChartCount; + _jobs[meshIdx] = job; + _meshToJobIdx[mesh] = meshIdx; + } + } + + public void Start() + { + for (uint meshIdx = 0; meshIdx < _jobs.Length; ++meshIdx) + { + _jobHandles[meshIdx] = _jobs[meshIdx].ScheduleByRef(); + } + } + + public MeshResult CompleteAndGetResult(Mesh mesh) + { + uint meshIdx = _meshToJobIdx[mesh]; + _jobHandles[meshIdx].Complete(); + var meshResult = new MeshResult + { + VertexChartIndices = _outputVertexChartIndices[meshIdx], + ChartCount = _outputChartCounts[meshIdx][0], + VertexChartIndicesIgnoringNormals = _outputVertexChartIndicesIgnoringNormals[meshIdx], + ChartCountIgnoringNormals = _outputChartCounts[meshIdx][1], + }; + return meshResult; + } + + public void Dispose() + { + for (uint meshIdx = 0; meshIdx < _jobs.Length; ++meshIdx) + _jobHandles[meshIdx].Complete(); + + foreach (var chartIndexList in _outputVertexChartIndices) + chartIndexList.Dispose(); + + foreach (var chartIndexList in _outputVertexChartIndicesIgnoringNormals) + chartIndexList.Dispose(); + + foreach (var chartCountList in _outputChartCounts) + chartCountList.Dispose(); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs.meta new file mode 100644 index 00000000000..1e83923cec4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartIdentification.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7a907a631dd46324681dd9b152a939c5 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs new file mode 100644 index 00000000000..4e9f5371943 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs @@ -0,0 +1,110 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal class ChartRasterizer : IDisposable + { + // Buffers required to execute chart rasterization + public struct Buffers + { + public GraphicsBuffer vertex; // Only used in software path + public GraphicsBuffer vertexToOriginalVertex; // Only used in software path + public GraphicsBuffer vertexToChartID; + } + + private readonly Material _softwareRasterizationMaterial; + private readonly Material _hardwareRasterizationMaterial; + + private static class ShaderProperties + { + public static readonly int VertexBuffer = Shader.PropertyToID("g_VertexBuffer"); + public static readonly int VertexToOriginalVertex = Shader.PropertyToID("g_VertexToOriginalVertex"); + public static readonly int VertexToChartID = Shader.PropertyToID("g_VertexToChartID"); + public static readonly int ScaleAndOffset = Shader.PropertyToID("g_ScaleAndOffset"); + public static readonly int ChartIndexOffset = Shader.PropertyToID("g_ChartIndexOffset"); + public static readonly int Width = Shader.PropertyToID("g_Width"); + public static readonly int Height = Shader.PropertyToID("g_Height"); + } + + public ChartRasterizer(Shader softwareRasterizationShader, Shader hardwareRasterizationShader) + { + _softwareRasterizationMaterial = new Material(softwareRasterizationShader); + _hardwareRasterizationMaterial = new Material(hardwareRasterizationShader); + } + + public void Dispose() + { + CoreUtils.Destroy(_softwareRasterizationMaterial); + CoreUtils.Destroy(_hardwareRasterizationMaterial); + } + +#if UNITY_EDITOR + public static void LoadShaders(out Shader softwareRasterizationShader, out Shader hardwareRasterizationShader) + { + softwareRasterizationShader = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader"); + hardwareRasterizationShader = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader"); + } +#endif + + private static Vector2[] SelectUVBuffer(Mesh from) + { + var uv2 = from.uv2; + if (uv2 != null && uv2.Length > 0) + return uv2; + else + return from.uv; + } + + public static void PrepareRasterizeSoftware(CommandBuffer cmd, Mesh from, GraphicsBuffer vertexBuffer, GraphicsBuffer vertexToOriginalVertexBuffer) + { + var originalUVs = from.vertices; + var originalIndices = from.triangles; + var uvs = new Vector2[originalIndices.Length]; + var vertexIds = new uint[originalIndices.Length]; + + for (int i = 0; i < originalIndices.Length; i++) + { + uint originalVertexIdx = (uint)originalIndices[i]; + uvs[i] = originalUVs[originalVertexIdx]; + vertexIds[i] = originalVertexIdx; + } + + cmd.SetBufferData(vertexBuffer, uvs); + cmd.SetBufferData(vertexToOriginalVertexBuffer, vertexIds); + } + + public void RasterizeSoftware(CommandBuffer cmd, GraphicsBuffer vertexBuffer, GraphicsBuffer vertexToOriginalVertexBuffer, GraphicsBuffer vertexToChartIdBuffer, uint indexCount, Vector4 scaleAndOffset, uint chartIndexOffset, RenderTexture destination) + { + cmd.SetGlobalBuffer(ShaderProperties.VertexBuffer, vertexBuffer); + cmd.SetGlobalBuffer(ShaderProperties.VertexToOriginalVertex, vertexToOriginalVertexBuffer); + cmd.SetGlobalBuffer(ShaderProperties.VertexToChartID, vertexToChartIdBuffer); + cmd.SetGlobalVector(ShaderProperties.ScaleAndOffset, scaleAndOffset); + cmd.SetGlobalInt(ShaderProperties.ChartIndexOffset, (int)chartIndexOffset); + cmd.SetGlobalInteger(ShaderProperties.Width, destination.width); + cmd.SetGlobalInteger(ShaderProperties.Height, destination.height); + + cmd.SetRenderTarget(destination); + cmd.DrawProcedural(Matrix4x4.identity, _softwareRasterizationMaterial, 0, MeshTopology.Triangles, (int)indexCount); + } + + public void RasterizeHardware(CommandBuffer cmd, Mesh mesh, GraphicsBuffer vertexToChartIdBuffer, Vector4 scaleAndOffset, uint chartIndexOffset, RenderTexture destination) + { + Debug.Assert(SystemInfo.supportsConservativeRaster, "Conservative rasterization is not supported on the current platform."); + + cmd.SetGlobalBuffer(ShaderProperties.VertexToChartID, vertexToChartIdBuffer); + cmd.SetGlobalVector(ShaderProperties.ScaleAndOffset, scaleAndOffset); + cmd.SetGlobalInt(ShaderProperties.ChartIndexOffset, (int)chartIndexOffset); + + cmd.SetRenderTarget(destination); + + for (int i = 0; i < mesh.subMeshCount; i++) + { + cmd.DrawMesh(mesh, Matrix4x4.identity, _hardwareRasterizationMaterial, i, 0); + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs.meta new file mode 100644 index 00000000000..c609e1c61eb --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/ChartRasterizer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 54a5cb4c515d9304ba7e7c9cab9ed90e \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs new file mode 100644 index 00000000000..ce229eb2d04 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Lightmapping +{ + // Detects pixels where multiple UV charts have overlapping bilinear neighborhoods. + internal class UVOverlapDetection : IDisposable + { + private static class ShaderProperties + { + public static int TextureSize = Shader.PropertyToID("_TextureSize"); + public static int PerPixelChart = Shader.PropertyToID("_PerPixelChart"); + public static int InstanceIndex = Shader.PropertyToID("_InstanceIndex"); + public static int EdgeCount = Shader.PropertyToID("_EdgeCount"); + public static int TriangleEdges = Shader.PropertyToID("_TriangleEdges"); + public static int ChartIndices = Shader.PropertyToID("_ChartIndices"); + public static int OverlapPixels = Shader.PropertyToID("_OverlapPixels"); + public static int OverlapInstances = Shader.PropertyToID("_OverlapInstances"); + public static int TileX = Shader.PropertyToID("_TileX"); + public static int TileY = Shader.PropertyToID("_TileY"); + public static int TileSize = Shader.PropertyToID("_TileSize"); + } + + private int _lightmapResolution; + + private ComputeShader _shader; + private NativeArray _triangleEdges; + private NativeArray _chartIndices; + private GraphicsBuffer _triangleEdgesBuffer; + private GraphicsBuffer _chartIndicesBuffer; + private GraphicsBuffer _perPixelChart; + private GraphicsBuffer _overlapPixelsBuffer; + private GraphicsBuffer _overlapInstancesBuffer; + + private int _overlapKernel; + private uint _overlapKernelSize; + + public void Initialize(ComputeShader shader, uint lightmapResolution, uint maxEdgeCount, uint instanceCount) + { + _lightmapResolution = (int)lightmapResolution; + + _shader = shader; + _triangleEdges = new NativeArray((int)maxEdgeCount,Allocator.Persistent); + _chartIndices = new NativeArray((int)maxEdgeCount, Allocator.Persistent); + _triangleEdgesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _triangleEdges.Length, UnsafeUtility.SizeOf()); + _chartIndicesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _chartIndices.Length, sizeof(uint)); + _perPixelChart = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _lightmapResolution*_lightmapResolution, sizeof(uint)); + _overlapPixelsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _lightmapResolution*_lightmapResolution, sizeof(uint)); + _overlapInstancesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)instanceCount, sizeof(uint)); + + _overlapKernel = shader.FindKernel("MarkBilinearOverlaps"); + shader.GetKernelThreadGroupSizes(_overlapKernel, out _overlapKernelSize, out _, out _); + + // Initialize buffers + _overlapPixelsBuffer.SetData(new uint[_lightmapResolution*_lightmapResolution]); + _overlapInstancesBuffer.SetData(new uint[instanceCount]); + var initPerPixelChart = new uint[_lightmapResolution * _lightmapResolution]; + Array.Fill(initPerPixelChart, uint.MaxValue); + _perPixelChart.SetData(initPerPixelChart); + } + + public void MarkOverlapsInInstance( + CommandBuffer cmd, + Mesh uvMesh, + NativeArray vertexToChartIndex, + float4 occupiedST, + uint instanceIndex, + uint chartIndexOffset) + { + // Get the start and end pos of every edge, and the chart index for each edge. + var indices = uvMesh.triangles; + var vertices = uvMesh.vertices; + for (uint triangleIdx = 0; triangleIdx < indices.Length / 3; triangleIdx++) + { + for (uint edgeOffset = 0; edgeOffset < 3; edgeOffset++) + { + uint baseTriangleIdx = triangleIdx * 3; + int startVertexIdx = indices[baseTriangleIdx + edgeOffset]; + int endVertexIdx = indices[baseTriangleIdx + ((edgeOffset + 1) % 3)]; + + float3 start = vertices[startVertexIdx]; + float3 end = vertices[endVertexIdx]; + start.xy = (start.xy * occupiedST.xy + occupiedST.zw) * _lightmapResolution; + end.xy = (end.xy * occupiedST.xy + occupiedST.zw) * _lightmapResolution; + _triangleEdges[(int)(baseTriangleIdx + edgeOffset)] = new float4(start.x, start.y, end.x, end.y); + + uint chartIdx = chartIndexOffset + vertexToChartIndex[startVertexIdx]; + _chartIndices[(int)(baseTriangleIdx + edgeOffset)] = chartIdx; + } + } + cmd.SetBufferData(_triangleEdgesBuffer, _triangleEdges); + cmd.SetBufferData(_chartIndicesBuffer, _chartIndices); + + // If the lightmap resolution is over this constant, we split the dispatch into multiple + // smaller dispatches over tiles, to prevent dispatches that are too large. Otherwise in + // the worst case, every lightmap texel can be checked in one dispatch. + const uint tileSize = 1024; + + // Mark overlaps + int edgeCount = indices.Length; + cmd.SetComputeIntParam(_shader, ShaderProperties.InstanceIndex, (int)instanceIndex); + cmd.SetComputeIntParam(_shader, ShaderProperties.EdgeCount, edgeCount); + cmd.SetComputeIntParam(_shader, ShaderProperties.TextureSize, _lightmapResolution); + cmd.SetComputeIntParam(_shader, ShaderProperties.TileSize, (int)tileSize); + cmd.SetComputeBufferParam(_shader, _overlapKernel, ShaderProperties.TriangleEdges, _triangleEdgesBuffer); + cmd.SetComputeBufferParam(_shader, _overlapKernel, ShaderProperties.ChartIndices, _chartIndicesBuffer); + cmd.SetComputeBufferParam(_shader, _overlapKernel, ShaderProperties.PerPixelChart, _perPixelChart); + cmd.SetComputeBufferParam(_shader, _overlapKernel, ShaderProperties.OverlapPixels, _overlapPixelsBuffer); + cmd.SetComputeBufferParam(_shader, _overlapKernel, ShaderProperties.OverlapInstances, _overlapInstancesBuffer); + int dispatchSize = GraphicsHelpers.DivUp(edgeCount, _overlapKernelSize); + + uint tileCountOnEachDim = GraphicsHelpers.DivUp((uint)_lightmapResolution, tileSize); + for (uint tileY = 0; tileY < tileCountOnEachDim; tileY++) + { + for (uint tileX = 0; tileX < tileCountOnEachDim; tileX++) + { + cmd.SetComputeIntParam(_shader, ShaderProperties.TileX, (int)tileX); + cmd.SetComputeIntParam(_shader, ShaderProperties.TileY, (int)tileY); + cmd.DispatchCompute(_shader, _overlapKernel, dispatchSize, 1, 1); + } + } + } + + public void CompactAndReadbackOverlaps( + CommandBuffer cmd, + out uint[] uniqueOverlapPixelIndices, + out ulong[] uniqueOverlapInstanceIndices) + { + // Make sure all kernels are finished + GraphicsHelpers.Flush(cmd); + + // Readback overlap buffers + uint[] overlapPixels = new uint[_overlapPixelsBuffer.count]; + _overlapPixelsBuffer.GetData(overlapPixels); + uint[] overlapInstances = new uint[_overlapInstancesBuffer.count]; + _overlapInstancesBuffer.GetData(overlapInstances); + + // Deduplicate overlaps + List uniqueOverlapPixelIndicesSet = new List(); + List uniqueOverlapInstanceIndicesSet = new List(); + for (uint pixelIndex = 0; pixelIndex < _overlapPixelsBuffer.count; pixelIndex++) + { + if (overlapPixels[pixelIndex] != 0) + uniqueOverlapPixelIndicesSet.Add(pixelIndex); + } + for (uint instanceIndex = 0; instanceIndex < _overlapInstancesBuffer.count; instanceIndex++) + { + if (overlapInstances[instanceIndex] != 0) + uniqueOverlapInstanceIndicesSet.Add(instanceIndex); + } + uniqueOverlapPixelIndices = new uint[uniqueOverlapPixelIndicesSet.Count]; + uniqueOverlapPixelIndicesSet.CopyTo(uniqueOverlapPixelIndices); + uniqueOverlapInstanceIndices = new ulong[uniqueOverlapInstanceIndicesSet.Count]; + uniqueOverlapInstanceIndicesSet.CopyTo(uniqueOverlapInstanceIndices); + + // Sort to keep the order deterministic + Array.Sort(uniqueOverlapPixelIndices); + Array.Sort(uniqueOverlapInstanceIndices); + } + + public void Dispose() + { + if (_triangleEdges.IsCreated) + _triangleEdges.Dispose(); + if (_chartIndices.IsCreated) + _chartIndices.Dispose(); + if (_triangleEdgesBuffer != null && _triangleEdgesBuffer.IsValid()) + _triangleEdgesBuffer.Dispose(); + if (_chartIndicesBuffer != null && _chartIndicesBuffer.IsValid()) + _chartIndicesBuffer.Dispose(); + if (_perPixelChart != null && _perPixelChart.IsValid()) + _perPixelChart.Dispose(); + if (_overlapPixelsBuffer != null && _overlapPixelsBuffer.IsValid()) + _overlapPixelsBuffer.Dispose(); + if (_overlapInstancesBuffer != null && _overlapInstancesBuffer.IsValid()) + _overlapInstancesBuffer.Dispose(); + } + +#if UNITY_EDITOR + public static ComputeShader LoadShader() + { + return UnityEditor.AssetDatabase.LoadAssetAtPath( + "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute"); + } +#endif + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs.meta new file mode 100644 index 00000000000..41ab4860e17 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Lightmapping/UVOverlapDetection.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e4ea9631f1f5a904d97ac50debf8006b \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs new file mode 100644 index 00000000000..5520402012f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs @@ -0,0 +1,246 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Integration; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Lightmapping +{ + internal class LightmappingContext: IDisposable + { + UnityComputeDeviceContext _deviceContext; + public UnityComputeWorld World; + public GraphicsBuffer TraceScratchBuffer; + public LightmapIntegratorContext IntegratorContext; + public LightmapIntegrationResourceCache ResourceCache; + public RenderTexture AccumulatedOutput; + public RenderTexture AccumulatedDirectionalOutput; + public GraphicsBuffer ExpandedOutput; + public GraphicsBuffer ExpandedDirectional; + public GraphicsBuffer GBuffer; + public GraphicsBuffer CompactedTexelIndices; + public GraphicsBuffer CompactedGBufferLength; + public GraphicsBuffer IndirectDispatchBuffer; + public GraphicsBuffer IndirectDispatchRayTracingBuffer; + + public ChartRasterizer ChartRasterizer; + // Temporary buffers required for chart rasterization + public ChartRasterizer.Buffers ChartRasterizerBuffers; + + private int _width; + private int _height; + public int Width => _width; + public int Height => _height; + + public void ClearOutputs() + { + CommandBuffer cmd = GetCommandBuffer(); + if (AccumulatedOutput is not null) + { + cmd.SetRenderTarget(AccumulatedOutput); + cmd.ClearRenderTarget(false, true, new Color(0.0f, 0.0f, 0.0f, 0.0f)); + } + if (AccumulatedDirectionalOutput is not null) + { + cmd.SetRenderTarget(AccumulatedDirectionalOutput); + cmd.ClearRenderTarget(false, true, new Color(0.0f, 0.0f, 0.0f, 0.0f)); + } + } + + static public RenderTexture MakeRenderTexture(int width, int height, string name) + { + RenderTextureDescriptor rtDesc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGBFloat, 0) + { + sRGB = false, + useMipMap = false, + autoGenerateMips = false, + enableRandomWrite = true, + dimension = TextureDimension.Tex2D, + volumeDepth = 1, + msaaSamples = 1, + vrUsage = VRTextureUsage.None, + memoryless = RenderTextureMemoryless.None, + useDynamicScale = false, + depthBufferBits = 0 + }; + RenderTexture renderTexture = new RenderTexture(rtDesc) + { + name = name, + enableRandomWrite = true, + hideFlags = HideFlags.HideAndDontSave + }; + return renderTexture; + } + + internal bool ExpandedBufferNeedsUpdating(UInt64 expandedSize) + { + if (ExpandedOutput is not null && + ExpandedDirectional is not null && + CompactedTexelIndices is not null && + GBuffer is not null && + expandedSize == (UInt64)ExpandedOutput.count && + expandedSize == (UInt64)ExpandedDirectional.count && + expandedSize == (UInt64)CompactedTexelIndices.count && + expandedSize == (UInt64)CompactedTexelIndices.count) + { + return false; + } + return true; + } + + internal bool InitializeExpandedBuffer(UInt64 expandedSize) + { + if (!ExpandedBufferNeedsUpdating(expandedSize)) + return true; + + ExpandedOutput?.Dispose(); + ExpandedOutput = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(expandedSize), sizeof(float) * 4); + if (ExpandedOutput is null) + { + Dispose(); + return false; + } + + ExpandedDirectional?.Dispose(); + ExpandedDirectional = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(expandedSize), sizeof(float) * 4); + if (ExpandedDirectional is null) + { + Dispose(); + return false; + } + + CompactedTexelIndices?.Dispose(); + CompactedTexelIndices = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(expandedSize), sizeof(uint)); + if (CompactedTexelIndices is null) + { + Dispose(); + return false; + } + + GBuffer?.Dispose(); + GBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(expandedSize), 16); // the GBuffer is used to store the UV samples as HitEntry (StochasticLightmapSampling.hlsl) hence the stride + + if (GBuffer is null) + { + Dispose(); + return false; + } + + return true; + } + + internal bool Initialize(UnityComputeDeviceContext deviceContext, int width, int height, UnityComputeWorld world, uint maxIndexCount, LightmapResourceLibrary resources) + { + _deviceContext = deviceContext; + World = world; + IntegratorContext = new LightmapIntegratorContext(); + ResourceCache = new LightmapIntegrationResourceCache(); + + ChartRasterizer = new ChartRasterizer(resources.SoftwareChartRasterizationShader, resources.HardwareChartRasterizationShader); + InitializeChartRasterizationBuffers(maxIndexCount); + + return SetOutputResolution(width, height); + } + + internal bool SetOutputResolution(int width, int height) // In case of failure, the context is disposed and is thus not usable anymore + { + _width = width; + _height = height; + + ReleaseAndDestroy(ref AccumulatedOutput); + AccumulatedOutput = MakeRenderTexture(width, height, "AccumulatedOutput"); + if (AccumulatedOutput == null || !AccumulatedOutput.Create()) + { + Dispose(); + return false; + } + + ReleaseAndDestroy(ref AccumulatedDirectionalOutput); + AccumulatedDirectionalOutput = MakeRenderTexture(width, height, "AccumulatedDirectionalOutput"); + if (AccumulatedDirectionalOutput == null || !AccumulatedDirectionalOutput.Create()) + { + Dispose(); + return false; + } + + CompactedGBufferLength?.Dispose(); + IndirectDispatchBuffer?.Dispose(); + IndirectDispatchRayTracingBuffer?.Dispose(); + CompactedGBufferLength = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, 1, sizeof(uint)); + IndirectDispatchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.CopyDestination, 3, sizeof(uint)); + IndirectDispatchRayTracingBuffer = RayTracingHelper.CreateDispatchIndirectBuffer(); + + return true; + } + + public void InitializeTraceScratchBuffer(uint width, uint height, uint expandedSampleWidth) + { + TraceScratchBuffer?.Dispose(); + TraceScratchBuffer = null; + + Debug.Assert(World is not null); + ulong scratchSize = World.RayTracingContext.GetRequiredTraceScratchBufferSizeInBytes(width, height, expandedSampleWidth); + uint scratchStride = RayTracingContext.GetScratchBufferStrideInBytes(); + if (scratchSize > 0) + { + TraceScratchBuffer = new GraphicsBuffer(RayTracingHelper.ScratchBufferTarget, (int)(scratchSize / scratchStride), 4); + Debug.Assert(TraceScratchBuffer == null || TraceScratchBuffer.target == RayTracingHelper.ScratchBufferTarget); + } + } + + private void InitializeChartRasterizationBuffers(uint maxIndexCount) + { + ChartRasterizerBuffers.vertex?.Dispose(); + ChartRasterizerBuffers.vertex = null; + ChartRasterizerBuffers.vertexToOriginalVertex?.Dispose(); + ChartRasterizerBuffers.vertexToOriginalVertex = null; + ChartRasterizerBuffers.vertexToChartID?.Dispose(); + ChartRasterizerBuffers.vertexToChartID = null; + + // We base the size of the temporary buffers on the triangle count of the mesh with the most triangles, to avoid constant reallocations. + ChartRasterizerBuffers.vertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)maxIndexCount, UnsafeUtility.SizeOf()); + ChartRasterizerBuffers.vertexToOriginalVertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)maxIndexCount, sizeof(uint)); + ChartRasterizerBuffers.vertexToChartID = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)maxIndexCount, sizeof(uint)); + } + + public CommandBuffer GetCommandBuffer() + { + Debug.Assert(_deviceContext != null); + return _deviceContext.GetCommandBuffer(); + } + + public void Dispose() + { + TraceScratchBuffer?.Dispose(); + ResourceCache?.Dispose(); + IntegratorContext?.Dispose(); + ReleaseAndDestroy(ref AccumulatedOutput); + ReleaseAndDestroy(ref AccumulatedDirectionalOutput); + ExpandedOutput?.Dispose(); + ExpandedDirectional?.Dispose(); + CompactedTexelIndices?.Dispose(); + GBuffer?.Dispose(); + CompactedGBufferLength?.Dispose(); + IndirectDispatchBuffer?.Dispose(); + IndirectDispatchRayTracingBuffer?.Dispose(); + + ChartRasterizerBuffers.vertex?.Dispose(); + ChartRasterizerBuffers.vertexToOriginalVertex?.Dispose(); + ChartRasterizerBuffers.vertexToChartID?.Dispose(); + + ChartRasterizer?.Dispose(); + ChartRasterizer = null; + } + + private static void ReleaseAndDestroy(ref RenderTexture tex) + { + if (tex == null) + return; + + tex.Release(); + CoreUtils.Destroy(tex); + tex = null; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs.meta new file mode 100644 index 00000000000..5570bf476c7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/LightmappingContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: db5319227b76429d9c98f680dfe63490 +timeCreated: 1720172589 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs new file mode 100644 index 00000000000..3cba2489d07 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Unity.Mathematics; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; +using static UnityEngine.PathTracing.Core.World; + +namespace UnityEngine.PathTracing.Core +{ + + // Interface for many light sampling + internal interface IManyLightSampling : IDisposable + { + void Build(CommandBuffer cmd, World.LightState lightState, Bounds sceneBounds, SamplingResources samplingResources); + void Bind(CommandBuffer cmd, IRayTracingShader shader); + } + + internal enum GridMemLayout { Sparse, Dense }; + internal enum GridSizingStrategy { Uniform, FitToSceneBounds }; + + internal static class LightGridUtils + { + public static Vector3Int ComputeLightGridDims(float3 sceneBounds, int maxLightGridCellCount, GridSizingStrategy lightGridSizingStrategy) + { + if (lightGridSizingStrategy == GridSizingStrategy.Uniform) + { + int volumeSide = (int)Math.Pow((double)maxLightGridCellCount, 1.0 / 3.0); + if ((volumeSide + 1) * (volumeSide + 1) * (volumeSide + 1) <= maxLightGridCellCount) + volumeSide++; + + return new Vector3Int(volumeSide, volumeSide, volumeSide); + } + + // Fix scene bounds if the ratio between 2 dims is too important + float maxSceneDim = math.max(sceneBounds.x, math.max(sceneBounds.y, sceneBounds.z)); + + if (sceneBounds.x * Mathf.Sqrt(maxLightGridCellCount) < maxSceneDim) + sceneBounds.x = maxSceneDim / Mathf.Sqrt(maxLightGridCellCount); + + if (sceneBounds.y * Mathf.Sqrt(maxLightGridCellCount) < maxSceneDim) + sceneBounds.y = maxSceneDim / Mathf.Sqrt(maxLightGridCellCount); + + if (sceneBounds.z * Mathf.Sqrt(maxLightGridCellCount) < maxSceneDim) + sceneBounds.z = maxSceneDim / Mathf.Sqrt(maxLightGridCellCount); + + // Compute ideal cell width (we aim for for cells having the same width along all 3 axes) + float idealCellWidth = Mathf.Pow(sceneBounds.x * sceneBounds.y * sceneBounds.z / ((float)maxLightGridCellCount), 1.0f / 3.0f); + + // Use ideal cell width to compute grid dims + Vector3Int gridDims = new Vector3Int( + Math.Max((int)(sceneBounds.x / idealCellWidth), 1), + Math.Max((int)(sceneBounds.y / idealCellWidth), 1), + Math.Max((int)(sceneBounds.z / idealCellWidth), 1)); + + Debug.Assert(gridDims.x * gridDims.y * gridDims.z <= maxLightGridCellCount); + return gridDims; + } + } + + + internal class ConservativeLightGrid : IManyLightSampling + { + // Light grid parameters + public int LightGridCellCount = 64 * 64 * 64; + public int MaxLightsPerCell = 64; // Only used with GridMemLayout.Dense + public GridSizingStrategy LightGridSizingStrategy = GridSizingStrategy.FitToSceneBounds; + public GridMemLayout GridMemLayout = GridMemLayout.Sparse; + + public ConservativeLightGrid(ComputeShader shader) + { + _shader = shader; + _buildLightGridlKernel = _shader.FindKernel("BuildConservativeLightGrid"); + } + + public void Init() + { + if (GridMemLayout == GridMemLayout.Dense && (_lightGridCellsDataBuffer == null || _lightGridCellsDataBuffer.count <= 1)) + { + int count = LightGridCellCount * MaxLightsPerCell; + int stride = Marshal.SizeOf(); + _lightGridCellsDataBuffer?.Dispose(); + _lightGridCellsDataBuffer = new ComputeBuffer(count, stride); + } + + if (_lightGridBuffer == null || _lightGridBuffer.count <= 1) + { + int count = LightGridCellCount; + int stride = Marshal.SizeOf(); + _lightGridBuffer?.Dispose(); + _lightGridBuffer = new ComputeBuffer(count, stride); + } + + if (_totalLightsInGridCountBuffer == null) + { + _totalLightsInGridCountBuffer = new ComputeBuffer(1, sizeof(int)); + } + } + + protected void BindComputeResources(CommandBuffer cmd, World.LightState lightState, Bounds sceneBounds, SamplingResources samplingResources) + { + SamplingResources.Bind(cmd, samplingResources); + + // Set the input lighting state.Note that this is a subset, as we evaluate without light cookies + cmd.SetComputeIntParam(_shader, ShaderProperties.NumLights, lightState.LightCount); + cmd.SetComputeIntParam(_shader, ShaderProperties.MaxLightsPerCell, MaxLightsPerCell); + cmd.SetComputeIntParam(_shader, ShaderProperties.NumEmissiveMeshes, lightState.MeshLightCount); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimX, _lightGridDims.x); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimY, _lightGridDims.y); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimZ, _lightGridDims.z); + cmd.SetComputeVectorParam(_shader, ShaderProperties.GridMin, sceneBounds.min); + cmd.SetComputeVectorParam(_shader, ShaderProperties.GridSize, sceneBounds.size); + cmd.SetComputeVectorParam(_shader, ShaderProperties.CellSize, _cellSize); + cmd.SetComputeVectorParam(_shader, ShaderProperties.InvCellSize, _invCellSize); + + cmd.SetComputeBufferParam(_shader, _buildLightGridlKernel, ShaderProperties.LightList, lightState.LightListBuffer); + + // Set the output buffer + cmd.SetComputeBufferParam(_shader, _buildLightGridlKernel, ShaderProperties.LightGrid, _lightGridBuffer); + cmd.SetComputeBufferParam(_shader, _buildLightGridlKernel, ShaderProperties.TotalReservoirCount, _totalLightsInGridCountBuffer); + } + + public void Build(CommandBuffer cmd, World.LightState lightState, Bounds sceneBounds, SamplingResources samplingResources) + { + if (lightState.LightListBuffer == null) + return; + + Init(); + + _sceneBounds = sceneBounds; + + _lightGridDims = LightGridUtils.ComputeLightGridDims(sceneBounds.size, LightGridCellCount, LightGridSizingStrategy); + float3 div = new float3(1.0f / _lightGridDims.x, 1.0f / _lightGridDims.y, 1.0f / _lightGridDims.z); + Vector3 cellSize = sceneBounds.size * div; + _cellSize = cellSize; + // The length of the diagonal + _cellSize.w = Mathf.Sqrt(cellSize.x * cellSize.x + cellSize.y * cellSize.y + cellSize.z * cellSize.z); + _invCellSize = new float4(1.0f) / _cellSize; + + BindComputeResources(cmd, lightState, sceneBounds, samplingResources); + + // If the grid is sparse, do a first dispatch to determine the total light count for all cells + // And allocate the _lightGridBuffer based on that number + if (GridMemLayout == GridMemLayout.Sparse) + { + _shader.EnableKeyword("SPARSE_GRID"); + cmd.SetComputeBufferParam(_shader, _buildLightGridlKernel, ShaderProperties.LightGridCellsData, _lightGridBuffer); // dummy bind + DispatchBuild(cmd, 0); + + GraphicsHelpers.Flush(cmd); + var requiredLightCount = new int[1]; + _totalLightsInGridCountBuffer.GetData(requiredLightCount); + if (_lightGridCellsDataBuffer == null || _lightGridCellsDataBuffer.count < requiredLightCount[0]) + { + _lightGridCellsDataBuffer?.Dispose(); + _lightGridCellsDataBuffer = new ComputeBuffer(math.max(requiredLightCount[0], 1), Marshal.SizeOf()); + } + + // Need to re-bind everything after flush + BindComputeResources(cmd, lightState, sceneBounds, samplingResources); + } + else + { + _shader.DisableKeyword("SPARSE_GRID"); + } + + // Build the grid + cmd.SetComputeBufferParam(_shader, _buildLightGridlKernel, ShaderProperties.LightGridCellsData, _lightGridCellsDataBuffer); + DispatchBuild(cmd, 1); + } + + public void Bind(CommandBuffer cmd, IRayTracingShader shader) + { + if (_lightGridCellsDataBuffer == null) + { + // dummy buffer, when the feature is disabled + int stride = Marshal.SizeOf(); + _lightGridCellsDataBuffer = new ComputeBuffer(1, stride); + } + + if (_lightGridBuffer == null) + { + // dummy buffer, when the feature is disabled + _lightGridBuffer = new ComputeBuffer(1, Marshal.SizeOf()); + } + + shader.SetIntParam(cmd, ShaderProperties.GridDimX, _lightGridDims.x); + shader.SetIntParam(cmd, ShaderProperties.GridDimY, _lightGridDims.y); + shader.SetIntParam(cmd, ShaderProperties.GridDimZ, _lightGridDims.z); + shader.SetIntParam(cmd, ShaderProperties.NumReservoirs, MaxLightsPerCell); + + shader.SetVectorParam(cmd, ShaderProperties.GridMin, _sceneBounds.min); + shader.SetVectorParam(cmd, ShaderProperties.GridSize, _sceneBounds.size); + shader.SetVectorParam(cmd, ShaderProperties.CellSize, _cellSize); + shader.SetVectorParam(cmd, ShaderProperties.InvCellSize, _invCellSize); + + shader.SetBufferParam(cmd, ShaderProperties.LightGridCellsData, _lightGridCellsDataBuffer); + shader.SetBufferParam(cmd, ShaderProperties.LightGrid, _lightGridBuffer); + } + + public void Dispose() + { + _lightGridCellsDataBuffer?.Dispose(); + _lightGridBuffer?.Dispose(); + _totalLightsInGridCountBuffer?.Dispose(); + } + + void DispatchBuild(CommandBuffer cmd, int buildPass) + { + const int groupDim = 4; + cmd.SetComputeIntParam(_shader, ShaderProperties.BuildPass, buildPass); + cmd.SetBufferData(_totalLightsInGridCountBuffer, new uint[] { 0 }); + cmd.DispatchCompute(_shader, _buildLightGridlKernel, + GraphicsHelpers.DivUp(_lightGridDims.x, groupDim), + GraphicsHelpers.DivUp(_lightGridDims.y, groupDim), + GraphicsHelpers.DivUp(_lightGridDims.z, groupDim)); + } + + readonly ComputeShader _shader; + readonly int _buildLightGridlKernel; + ComputeBuffer _lightGridCellsDataBuffer; + ComputeBuffer _lightGridBuffer; + ComputeBuffer _totalLightsInGridCountBuffer; + Bounds _sceneBounds; + Vector4 _cellSize; + Vector4 _invCellSize; + Vector3Int _lightGridDims; + } + + internal class RegirLightGrid : IManyLightSampling + { + // Light grid parameters + public int LightGridCellCount = 64 * 64 * 64; + public int MaxLightsPerCell = 64; + public int NumCandidates = -1; // -1 means we iterate over all the lights + public GridSizingStrategy LightGridSizingStrategy = GridSizingStrategy.Uniform; + + public RegirLightGrid(ComputeShader shader) + { + _shader = shader; + _buildRegirLightGridlKernel = _shader.FindKernel("BuildRegirLightGrid"); + } + + public void Init() + { + if (_lightGridCellsDataBuffer == null || _lightGridCellsDataBuffer.count <= 1) + { + int count = LightGridCellCount * MaxLightsPerCell; + int stride = Marshal.SizeOf(); + _lightGridCellsDataBuffer?.Dispose(); + _lightGridCellsDataBuffer = new ComputeBuffer(count, stride); + } + + if (_lightGridBuffer == null || _lightGridBuffer.count <= 1) + { + int count = LightGridCellCount; + int stride = Marshal.SizeOf(); + _lightGridBuffer?.Dispose(); + _lightGridBuffer = new ComputeBuffer(count, stride); + } + } + + public void Build(CommandBuffer cmd, World.LightState lightState, Bounds sceneBounds, SamplingResources samplingResources) + { + if (lightState.LightListBuffer == null) + return; + + Init(); + + _sceneBounds = sceneBounds; + + // The number of RIS candidates cannot exceed the number of light sources + int activeCandidates = NumCandidates == -1 ? lightState.LightCount : Mathf.Min(NumCandidates, lightState.LightCount); + + _lightGridDims = LightGridUtils.ComputeLightGridDims(sceneBounds.size, LightGridCellCount, LightGridSizingStrategy); + float3 div = new float3(1.0f / _lightGridDims.x, 1.0f / _lightGridDims.y, 1.0f / _lightGridDims.z); + Vector3 cellSize = sceneBounds.size * div; + _cellSize = cellSize; + // The length of the diagonal + _cellSize.w = Mathf.Sqrt(cellSize.x * cellSize.x + cellSize.y * cellSize.y + cellSize.z * cellSize.z); + _invCellSize = new float4(1.0f) / _cellSize; + + SamplingResources.Bind(cmd, samplingResources); + + // Set the input lighting state.Note that this is a subset, as we evaluate without light cookies + cmd.SetComputeIntParam(_shader, ShaderProperties.NumLights, lightState.LightCount); + cmd.SetComputeIntParam(_shader, ShaderProperties.NumCandidates, activeCandidates); + cmd.SetComputeIntParam(_shader, ShaderProperties.NumReservoirs, MaxLightsPerCell); + cmd.SetComputeIntParam(_shader, ShaderProperties.NumEmissiveMeshes, lightState.MeshLightCount); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimX, _lightGridDims.x); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimY, _lightGridDims.y); + cmd.SetComputeIntParam(_shader, ShaderProperties.GridDimZ, _lightGridDims.z); + cmd.SetComputeVectorParam(_shader, ShaderProperties.GridMin, sceneBounds.min); + cmd.SetComputeVectorParam(_shader, ShaderProperties.GridSize, sceneBounds.size); + cmd.SetComputeVectorParam(_shader, ShaderProperties.CellSize, _cellSize); + cmd.SetComputeVectorParam(_shader, ShaderProperties.InvCellSize, _invCellSize); + + cmd.SetComputeBufferParam(_shader, _buildRegirLightGridlKernel, ShaderProperties.LightList, lightState.LightListBuffer); + + // Set the output buffer + cmd.SetComputeBufferParam(_shader, _buildRegirLightGridlKernel, ShaderProperties.LightGrid, _lightGridBuffer); + cmd.SetComputeBufferParam(_shader, _buildRegirLightGridlKernel, ShaderProperties.LightGridCellsData, _lightGridCellsDataBuffer); + + // Build the grid + const int groupDim = 4; + cmd.DispatchCompute(_shader, _buildRegirLightGridlKernel, + GraphicsHelpers.DivUp(_lightGridDims.x, groupDim), + GraphicsHelpers.DivUp(_lightGridDims.y, groupDim), + GraphicsHelpers.DivUp(_lightGridDims.z, groupDim)); + } + + public void Bind(CommandBuffer cmd, IRayTracingShader shader) + { + if (_lightGridCellsDataBuffer == null) + { + // dummy buffer, when the feature is disabled + int stride = Marshal.SizeOf(); + _lightGridCellsDataBuffer = new ComputeBuffer(1, stride); + } + + if (_lightGridBuffer == null) + { + // dummy buffer, when the feature is disabled + _lightGridBuffer = new ComputeBuffer(1, Marshal.SizeOf()); + } + + shader.SetIntParam(cmd, ShaderProperties.GridDimX, _lightGridDims.x); + shader.SetIntParam(cmd, ShaderProperties.GridDimY, _lightGridDims.y); + shader.SetIntParam(cmd, ShaderProperties.GridDimZ, _lightGridDims.z); + shader.SetIntParam(cmd, ShaderProperties.NumReservoirs, MaxLightsPerCell); + + shader.SetVectorParam(cmd, ShaderProperties.GridMin, _sceneBounds.min); + shader.SetVectorParam(cmd, ShaderProperties.GridSize, _sceneBounds.size); + shader.SetVectorParam(cmd, ShaderProperties.CellSize, _cellSize); + shader.SetVectorParam(cmd, ShaderProperties.InvCellSize, _invCellSize); + + shader.SetBufferParam(cmd, ShaderProperties.LightGridCellsData, _lightGridCellsDataBuffer); + shader.SetBufferParam(cmd, ShaderProperties.LightGrid, _lightGridBuffer); + } + + public void Dispose() + { + _lightGridCellsDataBuffer?.Dispose(); + _lightGridBuffer?.Dispose(); + } + + + readonly ComputeShader _shader; + readonly int _buildRegirLightGridlKernel; + ComputeBuffer _lightGridCellsDataBuffer; + ComputeBuffer _lightGridBuffer; + Bounds _sceneBounds; + Vector4 _cellSize; + Vector4 _invCellSize; + Vector3Int _lightGridDims; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs.meta new file mode 100644 index 00000000000..eece2e95b85 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ManyLightSampling.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 79729a586fa84f249a56f19de16c118e diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs new file mode 100644 index 00000000000..2604a41d0d4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs @@ -0,0 +1,209 @@ +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Core +{ + internal enum MaterialPropertyType { None = 0, Color, Texture } + + internal struct MaterialPropertyDesc + { + public MaterialPropertyType Type; + public float3 Color; // Unused if type == Textured + } + + internal enum TransmissionChannels { None = 0, RGB, Alpha } + + internal struct TransmissionDesc + { + public Texture SourceTexture; + public TransmissionChannels Channels; + public Vector2 Scale; + public Vector2 Offset; + } + + internal static class MaterialAspectOracle + { + // See gi::HasBakedEmissive in Materials.cpp + public static MaterialPropertyDesc GetEmission(Material mat) + { + // If the material is not marked as baked emissive, or if it is black, there is no emission + var emissiveFlags = mat.globalIlluminationFlags; + bool isBlack = emissiveFlags.HasFlag(MaterialGlobalIlluminationFlags.EmissiveIsBlack); + bool isBaked = emissiveFlags.HasFlag(MaterialGlobalIlluminationFlags.BakedEmissive); + if (isBlack || !isBaked) + { + return new MaterialPropertyDesc { Type = MaterialPropertyType.None, Color = float3.zero }; + } + + // If the material has an emission keyword, but it is disabled, there is no emission + if (IsMaterialWithEmissionKeyword(mat) && !EnumerableArrayContains(mat.shaderKeywords, "_EMISSION")) + { + return new MaterialPropertyDesc { Type = MaterialPropertyType.None, Color = float3.zero }; + } + + // If we have reached this point, the material should be emissive. + // First we check for an emissive texture, and use that if it exists + if (HasEmissionMap(mat)) + { + return new MaterialPropertyDesc { Type = MaterialPropertyType.Texture, Color = float3.zero }; + } + + // Otherwise, if the material only has an emissive color, we use that + if (mat.HasProperty(SID.EmissionColor)) + { + return new MaterialPropertyDesc { Type = MaterialPropertyType.Color, Color = ToFloat3(mat.GetColor(SID.EmissionColor)) }; + } + + // If we found neither property, we assume that the material has an unusual meta pass implementation, + // which will render the emission - thus we use texture mode + return new MaterialPropertyDesc { Type = MaterialPropertyType.Texture, Color = float3.zero }; + } + + private static bool EnumerableArrayContains(IEnumerable array, string value) + { + foreach (string element in array) + if (EqualityComparer.Default.Equals(element, value)) + return true; + + return false; + } + + // Check if a material has a property with a specific Shaderlab property flag + private static bool MaterialHasPropertyWithFlag(Material mat, ShaderPropertyFlags flag) + { + if (mat.shader == null) + return false; + + int numProperties = mat.shader.GetPropertyCount(); + for (int i = 0; i < numProperties; ++i) + { + var flags = mat.shader.GetPropertyFlags(i); + if (flags.HasFlag(flag)) + { + return true; + } + } + return false; + } + + // See CreateBakeMaterial in ExtractBakeMaterials.cpp + public static TransmissionDesc GetTransmission(Material mat) + { + // Full RGB transmission + bool hasRGBTransparencyTexture = mat.HasProperty(SID.TransparencyLm) && mat.GetTexture(SID.TransparencyLm) != null; + if (hasRGBTransparencyTexture) + { + return new TransmissionDesc + { + Channels = TransmissionChannels.RGB, + SourceTexture = mat.GetTexture(SID.TransparencyLm), + Scale = mat.GetTextureScale(SID.TransparencyLm), + Offset = mat.GetTextureOffset(SID.TransparencyLm) + }; + } + + // Alpha-only transmission, alpha from main texture (if exists) + bool isOnTransparentQueue = mat.renderQueue >= (int)RenderQueue.AlphaTestRenderQueue && mat.renderQueue < (int)RenderQueue.OverlayRenderQueue; + bool hasMainTexture = MaterialHasPropertyWithFlag(mat, ShaderPropertyFlags.MainTexture) || mat.HasProperty(SID.MainTex); + if (isOnTransparentQueue && hasMainTexture) + { + return new TransmissionDesc + { + Channels = TransmissionChannels.Alpha, + SourceTexture = mat.mainTexture, + Scale = mat.mainTextureScale, + Offset = mat.mainTextureOffset + }; + } + + // No transmission + return new TransmissionDesc { Channels = TransmissionChannels.None }; + } + + private static bool HasEmissionMap(Material mat) + { + return (mat.HasProperty(SID.EmissionMap) && mat.GetTexture(SID.EmissionMap) != null) + && (!mat.HasProperty(SID.UseEmissiveMap) || mat.GetInt(SID.UseEmissiveMap) == 1); + } + + private static bool IsMaterialWithEmissionKeyword(Material mat) + { + return EnumerableArrayContains(mat.shader.keywordSpace.keywordNames, "_EMISSION"); + } + + public static float GetAlpha(Material mat) + { + if (mat.HasProperty(SID.BaseColor)) + { + return mat.GetColor(SID.BaseColor).a; + } + else if (mat.HasProperty(SID.Color)) + { + return mat.GetColor(SID.Color).a; + } + else + { + for (int i = 0; i < mat.shader.GetPropertyCount(); i++) + { + if (mat.shader.GetPropertyFlags(i).HasFlag(ShaderPropertyFlags.MainColor)) + { + return mat.GetColor(mat.shader.GetPropertyNameId(i)).a; + } + } + + return 1.0f; + } + } + + public static bool UsesAlphaCutoff(Material mat) + { + bool alphaTestQueue = mat.renderQueue >= (int)RenderQueue.AlphaTestRenderQueue && mat.renderQueue < (int)RenderQueue.TransparentRenderQueue; + if (!alphaTestQueue) + return false; + + return mat.HasProperty(SID.Cutoff) || mat.HasProperty(SID.AlphaTestRef); + } + + public static float GetAlphaCutoff(Material mat) + { + if (mat.HasProperty(SID.Cutoff)) + { + return mat.GetFloat(SID.Cutoff); + } + else if (mat.HasProperty(SID.AlphaTestRef)) + { + return mat.GetFloat(SID.AlphaTestRef); + } + return 0.0f; + } + + private static float3 ToFloat3(Color col) + { + return new float3(col.r, col.g, col.b); + } + + private enum RenderQueue + { + GeometryRenderQueue = 2000, + AlphaTestRenderQueue = 2450, + TransparentRenderQueue = 3000, + OverlayRenderQueue = 4000, + } + + private static class SID + { + public static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor"); + public static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap"); + public static readonly int UseEmissiveMap = Shader.PropertyToID("_UseEmissiveMap"); + public static readonly int TransparencyLm = Shader.PropertyToID("_TransparencyLM"); + public static readonly int Color = Shader.PropertyToID("_Color"); + public static readonly int BaseColor = Shader.PropertyToID("_BaseColor"); + public static readonly int Cutoff = Shader.PropertyToID("_Cutoff"); + public static readonly int AlphaTestRef = Shader.PropertyToID("_AlphaTestRef"); + public static readonly int MainTex = Shader.PropertyToID("_MainTex"); + } + + // TODO(Yvain) Bump, roughness, metalness ? + } +} diff --git a/Packages/com.unity.render-pipelines.universal/Editor/Converter/MaterialReferenceBuilder.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs.meta similarity index 83% rename from Packages/com.unity.render-pipelines.universal/Editor/Converter/MaterialReferenceBuilder.cs.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs.meta index 6b6d4efc57d..15baf83bbe0 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/Converter/MaterialReferenceBuilder.cs.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialAspectOracle.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 630a22f725821a446b26d9393ed2a8c4 +guid: 57a4583d3447a474ab6db0deedbaeb0b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs new file mode 100644 index 00000000000..7adb59a5756 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs @@ -0,0 +1,761 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine.Assertions; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Core +{ + internal enum UVChannel : uint { UV0 = 0, UV1 = 1 }; + + internal class MaterialPool : IDisposable + { + [Flags] + private enum MaterialFlags : uint + { + None = 0, + IsTransmissive = 1 << 0, + DoubleSidedGI = 1 << 1, + PointSampleTransmission = 1 << 2, + } + + private struct PTMaterial + { + public int AlbedoTextureIndex; + public int EmissionTextureIndex; + public int TransmissionTextureIndex; + public MaterialFlags Flags; + public float2 AlbedoScale; + public float2 AlbedoOffset; + public float2 EmissionScale; + public float2 EmissionOffset; + public float2 TransmissionScale; + public float2 TransmissionOffset; + public float3 EmissionColor; + public UVChannel AlbedoAndEmissionUVChannel; + }; + + private enum TextureType { Albedo, Emission, Transmission, LightCookie, LightCubemap }; + + private class MaterialEntry + { + public MaterialEntry(BlockAllocator.Allocation indexInBufferAlloc) + { + IndexInBuffer = indexInBufferAlloc; + } + + public readonly BlockAllocator.Allocation IndexInBuffer; + public TextureSlotAllocator.TextureLocation AlbedoTextureLocation = TextureSlotAllocator.TextureLocation.Invalid; + public TextureSlotAllocator.TextureLocation EmissionTextureLocation = TextureSlotAllocator.TextureLocation.Invalid; + public TextureSlotAllocator.TextureLocation TransmissionTextureLocation = TextureSlotAllocator.TextureLocation.Invalid; + public float3 EmissionColor = new(0, 0, 0); + public UVChannel AlbedoAndEmissionUVChannel = 0; + public bool DoubleSidedGI; + public bool IsTransmissive; + public bool PointSampleTransmission; + }; + + private readonly Dictionary _materials = new(); + private readonly Dictionary _lightCookies = new(); // this dictionary is used to keep track of which textures are inserted in the atlas and avoid duplication (important for LiveGI) + private readonly Dictionary _cookieHandleToID = new(); // this dictionary is used when deleting lights with cookies attached, so we can free-up the atlas slots + + public int MaterialCount => _materials.Count; + private BlockAllocator _materialSlotAllocator; + public ComputeBuffer MaterialBuffer; + private NativeArray _materialList; + private bool _materialArrayDirty = true; + + private readonly TextureSlotAllocator _albedoTextureAllocator; + private readonly TextureSlotAllocator _emissionTextureAllocator; + private readonly TextureSlotAllocator _transmissionTextureAllocator; + public RenderTexture AlbedoTextures => _albedoTextureAllocator.Texture; + public RenderTexture EmissionTextures => _emissionTextureAllocator.Texture; + public RenderTexture TransmissionTextures => _transmissionTextureAllocator.Texture; + + private static Mesh _planeMesh; + public RenderTexture LightCookieTextures; + private BlockAllocator _lightCookieTexturesSlotAllocator; + + public RenderTexture LightCubemapTextures; + private BlockAllocator _lightCubemapTexturesSlotAllocator; + + private readonly ComputeShader _setAlphaChannelShader; + private readonly int _setAlphaChannelKernel; + private readonly uint3 _alphaShaderThreadGroupSizes; + + private readonly ComputeShader _blitCubemapShader; + private readonly int _blitCubemapKernel; + + private readonly ComputeShader _blitGrayscaleCookieShader; + private readonly int _blitGrayscaleCookieKernel; + + private const int AtlasSize = 256; + + public MaterialPool(ComputeShader setAlphaChannelShader, ComputeShader blitCubemapShader, ComputeShader blitGrayscaleCookieShader) + { + _setAlphaChannelShader = setAlphaChannelShader; + _setAlphaChannelKernel = _setAlphaChannelShader.FindKernel("SetAlphaChannel"); + _setAlphaChannelShader.GetKernelThreadGroupSizes(_setAlphaChannelKernel, + out _alphaShaderThreadGroupSizes.x, out _alphaShaderThreadGroupSizes.y, out _alphaShaderThreadGroupSizes.z); + + _blitCubemapShader = blitCubemapShader; + _blitCubemapKernel = _blitCubemapShader.FindKernel("BlitCubemap"); + + _blitGrayscaleCookieShader = blitGrayscaleCookieShader; + _blitGrayscaleCookieKernel = _blitGrayscaleCookieShader.FindKernel("BlitGrayScaleCookie"); + + const int initialMaxLightCookieTextureCount = 30; + const int initialMaxLightCubemapTextureCount = 3; + const int initialMaxMaterialCount = 100; + + _albedoTextureAllocator = new TextureSlotAllocator(AtlasSize, GetTextureFormat(TextureType.Albedo), FilterMode.Bilinear); + _emissionTextureAllocator = new TextureSlotAllocator(AtlasSize, GetTextureFormat(TextureType.Emission), FilterMode.Bilinear); + _transmissionTextureAllocator = new TextureSlotAllocator(AtlasSize, GetTextureFormat(TextureType.Transmission), FilterMode.Bilinear); + + _materialSlotAllocator.Initialize(initialMaxMaterialCount); + MaterialBuffer = new ComputeBuffer(initialMaxMaterialCount, Marshal.SizeOf()); + _materialList = new NativeArray(initialMaxMaterialCount, Allocator.Persistent); + + LightCookieTextures = CreateTextureArray(initialMaxLightCookieTextureCount, TextureType.LightCookie); + _lightCookieTexturesSlotAllocator.Initialize(initialMaxLightCookieTextureCount); + + LightCubemapTextures = CreateTextureArray(initialMaxLightCubemapTextureCount, TextureType.LightCubemap); + _lightCubemapTexturesSlotAllocator.Initialize(initialMaxLightCubemapTextureCount); + } + + public void Dispose() + { + _materialSlotAllocator.Dispose(); + MaterialBuffer?.Dispose(); + if (_materialList.IsCreated) + _materialList.Dispose(); + + _albedoTextureAllocator.Dispose(); + _emissionTextureAllocator.Dispose(); + _transmissionTextureAllocator.Dispose(); + + _lightCookieTexturesSlotAllocator.Dispose(); + if (LightCookieTextures != null) + { + LightCookieTextures.Release(); + CoreUtils.Destroy(LightCookieTextures); + } + + _lightCubemapTexturesSlotAllocator.Dispose(); + if (LightCubemapTextures != null) + { + LightCubemapTextures.Release(); + CoreUtils.Destroy(LightCubemapTextures); + } + } + + public void AddMaterial(UInt64 materialHandle, in World.MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) + { + MaterialEntry materialEntry; + Debug.Assert(!_materials.ContainsKey(materialHandle), "Material was already added to the pool."); + + var slotAllocation = _materialSlotAllocator.Allocate(1); + if (!slotAllocation.valid) + { + _materialSlotAllocator.Grow(_materialSlotAllocator.capacity+1); + RecreateMaterialList(); + slotAllocation = _materialSlotAllocator.Allocate(1); + Assert.IsTrue(slotAllocation.valid); + } + + materialEntry = new MaterialEntry(slotAllocation); + UpdateMaterial(in material, albedoAndEmissionUVChannel, materialEntry); + + _materials.Add(materialHandle, materialEntry); + + UpdateMaterialList(materialEntry); + } + + public void UpdateMaterial(UInt64 materialHandle, in World.MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) + { + MaterialEntry materialEntry = _materials[materialHandle]; + UpdateMaterial(in material, albedoAndEmissionUVChannel, materialEntry); + } + + public void RemoveMaterial(UInt64 materialInstanceID) + { + if (!_materials.TryGetValue(materialInstanceID, out var materialEntry)) + { + Debug.Assert(false, "Material was not added to the pool."); + return; + } + + _materialSlotAllocator.FreeAllocation(in materialEntry.IndexInBuffer); + RemoveTextureIfPresent(TextureType.Albedo, ref materialEntry.AlbedoTextureLocation); + RemoveTextureIfPresent(TextureType.Emission, ref materialEntry.EmissionTextureLocation); + RemoveTextureIfPresent(TextureType.Transmission, ref materialEntry.TransmissionTextureLocation); + + _materials.Remove(materialInstanceID); + } + + public void GetMaterialInfo(UInt64 materialHandle, out uint materialIndex, out bool isTransmissive) + { + MaterialEntry entry; + if (_materials.TryGetValue(materialHandle, out entry)) + { + materialIndex = (uint)entry.IndexInBuffer.block.offset; + isTransmissive = entry.IsTransmissive; + } + else + throw new InvalidOperationException("Material not in pool"); + } + + public bool IsEmissive(UInt64 materialHandle, out float3 emissionColor) + { + float3 zero = new(0, 0, 0); + emissionColor = zero; + + if (_materials.TryGetValue(materialHandle, out var entry)) + { + if (entry.EmissionTextureLocation.IsValid || math.any(entry.EmissionColor != zero)) + { + emissionColor = entry.EmissionColor; + return true; + } + return false; + } + else + return false; + } + + public void Build(CommandBuffer cmd) + { + if (cmd is null) + throw new InvalidOperationException("Invalid CommandBuffer, did you forget to call Initialize()?"); + if (_materialArrayDirty) + { + cmd.SetBufferData(MaterialBuffer, _materialList); + _materialArrayDirty = false; + } + } + + private void UpdateMaterial(in World.MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel, MaterialEntry materialEntry) + { + AddOrUpdateTexture(in material, TextureType.Albedo, ref materialEntry.AlbedoTextureLocation); + + if (material.TransmissionChannels == TransmissionChannels.Alpha) + FillAlbedoTextureAlphaWithOpacity(in material, materialEntry.AlbedoTextureLocation); + + materialEntry.AlbedoAndEmissionUVChannel = albedoAndEmissionUVChannel; + materialEntry.DoubleSidedGI = material.DoubleSidedGI; + materialEntry.IsTransmissive = material.TransmissionChannels != TransmissionChannels.None; + materialEntry.PointSampleTransmission = material.PointSampleTransmission; + + var emissionType = material.EmissionType; + if (emissionType == MaterialPropertyType.Texture) + { + AddOrUpdateTexture(in material, TextureType.Emission, ref materialEntry.EmissionTextureLocation); + materialEntry.EmissionColor = 0; + } + else if (emissionType == MaterialPropertyType.Color) + { + RemoveTextureIfPresent(TextureType.Emission, ref materialEntry.EmissionTextureLocation); + materialEntry.EmissionColor = material.EmissionColor; + } + else if (emissionType == MaterialPropertyType.None) + { + RemoveTextureIfPresent(TextureType.Emission, ref materialEntry.EmissionTextureLocation); + materialEntry.EmissionColor = 0; + } + + if (material.TransmissionChannels == TransmissionChannels.RGB && material.Transmission != null) + { + AddOrUpdateTexture(in material, TextureType.Transmission, ref materialEntry.TransmissionTextureLocation); + } + else + { + RemoveTextureIfPresent(TextureType.Transmission, ref materialEntry.TransmissionTextureLocation); + } + + UpdateMaterialList(materialEntry); + } + + private void UpdateMaterialList(MaterialEntry entry) + { + // Default values, filled in below + var gpuMat = new PTMaterial() + { + // Default values, filled in below + AlbedoTextureIndex = -1, + EmissionTextureIndex = -1, + TransmissionTextureIndex = -1, + AlbedoScale = Vector2.one, + AlbedoOffset = Vector2.zero, + EmissionScale = Vector2.one, + EmissionOffset = Vector2.zero, + TransmissionScale = Vector2.one, + TransmissionOffset = Vector2.zero, + + // Values to be filled in immediately + EmissionColor = entry.EmissionColor, + AlbedoAndEmissionUVChannel = entry.AlbedoAndEmissionUVChannel, + Flags = (entry.IsTransmissive ? MaterialFlags.IsTransmissive : MaterialFlags.None) | + (entry.DoubleSidedGI ? MaterialFlags.DoubleSidedGI : MaterialFlags.None) | + (entry.PointSampleTransmission ? MaterialFlags.PointSampleTransmission : MaterialFlags.None), + }; + + if (entry.AlbedoTextureLocation.IsValid) + { + gpuMat.AlbedoTextureIndex = entry.AlbedoTextureLocation.AtlasIndex; + _albedoTextureAllocator.GetScaleAndOffset(in entry.AlbedoTextureLocation, out var albedoScale, out var albedoOffset); + gpuMat.AlbedoScale = albedoScale; + gpuMat.AlbedoOffset = albedoOffset; + } + if (entry.EmissionTextureLocation.IsValid) + { + gpuMat.EmissionTextureIndex = entry.EmissionTextureLocation.AtlasIndex; + _emissionTextureAllocator.GetScaleAndOffset(in entry.EmissionTextureLocation, out var emissionScale, out var emissionOffset); + gpuMat.EmissionScale = emissionScale; + gpuMat.EmissionOffset = emissionOffset; + } + if (entry.TransmissionTextureLocation.IsValid) + { + gpuMat.TransmissionTextureIndex = entry.TransmissionTextureLocation.AtlasIndex; + _transmissionTextureAllocator.GetScaleAndOffset(in entry.TransmissionTextureLocation, out var transmissionScale, out var transmissionOffset); + gpuMat.TransmissionScale = transmissionScale; + gpuMat.TransmissionOffset = transmissionOffset; + } + + _materialList[entry.IndexInBuffer.block.offset] = gpuMat; + _materialArrayDirty = true; + } + + #region Cookie support + private static int GetCookieFaces(Texture tex) + { + return (tex.dimension == TextureDimension.Cube) ? 6 : 1; + } + + private static int GetCookieHandle(int slices, int handle) + { + int offset = (slices > 1) ? 1 : 0; + // use the first bit to indicate the array type + return 2 * handle + offset; + } + + public int AddCookieTexture(Texture cookie) + { + if (cookie != null) + { + var cookieID = Util.EntityIDToUlong(cookie.GetEntityId()); + if (_lightCookies.TryGetValue(cookieID, out var allocator)) + { + // This cookie was already copied in the atlas in a previous frame + // Baking will never hit this path, as the lights are not updated during a bake, but it's important for LiveGI + return allocator.handle; + } + + int slices = GetCookieFaces(cookie); + + BlockAllocator.Allocation slotAllocation; + if (slices > 1) + { + slotAllocation = _lightCubemapTexturesSlotAllocator.Allocate(1); + ExpandCookieTextureArray(true, ref _lightCubemapTexturesSlotAllocator, ref LightCubemapTextures, ref slotAllocation); + + RenderTexture prevRT = RenderTexture.active; + BlitCubemapCookie(cookie, LightCubemapTextures, slotAllocation.block.offset); + RenderTexture.active = prevRT; + } + else + { + slotAllocation = _lightCookieTexturesSlotAllocator.Allocate(1); + ExpandCookieTextureArray(false, ref _lightCookieTexturesSlotAllocator, ref LightCookieTextures, ref slotAllocation); + + RenderTexture prevRT = RenderTexture.active; + Blit2DCookie(cookie, LightCookieTextures, slotAllocation.block.offset); + RenderTexture.active = prevRT; + } + + int handle = GetCookieHandle(slices, slotAllocation.handle); + _lightCookies.Add(cookieID, slotAllocation); + _cookieHandleToID.Add(handle, cookieID); + return slotAllocation.handle; + } + + return -1; + } + + public void RemoveCookieTexture(int cookieFaces, int cookieIndex) + { + if (cookieIndex == -1) + return; + + int cookieHandle = GetCookieHandle(cookieFaces, cookieIndex); + if (!_cookieHandleToID.TryGetValue(cookieHandle, out var cookieID)) + { + if (!_lightCookies.TryGetValue(cookieID, out var cookieAllocation)) + { + Debug.Assert(false, "Light cookie was not found in the pool."); + return; + } + + if (cookieFaces > 1) + _lightCubemapTexturesSlotAllocator.FreeAllocation(in cookieAllocation); + else + _lightCookieTexturesSlotAllocator.FreeAllocation(in cookieAllocation); + + _lightCookies.Remove(cookieID); + _cookieHandleToID.Remove(cookieHandle); + } + } + + private void ExpandCookieTextureArray(bool isCubemapCookie, ref BlockAllocator allocator, ref RenderTexture texture, ref BlockAllocator.Allocation textureAlloc) + { + if (!textureAlloc.valid) + { + textureAlloc = allocator.Allocate(1); + if (!textureAlloc.valid) + { + var oldCapacity = allocator.capacity; + allocator.Grow(allocator.capacity + 1); + + var newTexture = CreateTextureArray(allocator.capacity, isCubemapCookie ? TextureType.LightCubemap : TextureType.LightCookie); + + using var cmd = new CommandBuffer(); + for (int i = 0; i < oldCapacity; ++i) + cmd.CopyTexture(texture, i, newTexture, i); + Graphics.ExecuteCommandBuffer(cmd); + + texture.Release(); + texture = newTexture; + + textureAlloc = allocator.Allocate(1); + Assert.IsTrue(textureAlloc.valid); + } + } + } + + // Blits all faces of a cubemap to a slice of a cubemap array, performing any format conversions that are necessary + private void BlitCubemapCookie(Texture source, RenderTexture dest, int destIndex) + { + using var cmd = new CommandBuffer(); + + RenderTexture tempRT = new RenderTexture(AtlasSize, AtlasSize, 0, dest.format, 0); + tempRT.enableRandomWrite = true; + tempRT.Create(); + + if (GraphicsFormatUtility.IsAlphaOnlyFormat(source.graphicsFormat)) + _blitCubemapShader.EnableKeyword("GRAYSCALE_BLIT"); + else + _blitCubemapShader.DisableKeyword("GRAYSCALE_BLIT"); + + for (int i = 0; i < 6; ++i) + { + cmd.SetComputeIntParam(_blitCubemapShader, Shader.PropertyToID("g_TextureSize"), dest.width); + cmd.SetComputeIntParam(_blitCubemapShader, Shader.PropertyToID("g_Face"), i); + cmd.SetComputeTextureParam(_blitCubemapShader, _blitCubemapKernel, Shader.PropertyToID("g_Source"), source); + cmd.SetComputeTextureParam(_blitCubemapShader, _blitCubemapKernel, Shader.PropertyToID("g_Destination"), tempRT); + int dispatchSize = GraphicsHelpers.DivUp(dest.width, 8); + cmd.DispatchCompute(_blitCubemapShader, _blitCubemapKernel, dispatchSize, dispatchSize, 1); + cmd.CopyTexture(tempRT, 0, dest, 6 * destIndex + i); + } + + Graphics.ExecuteCommandBuffer(cmd); + tempRT.Release(); + CoreUtils.Destroy(tempRT); + } + + private void Blit2DCookie(Texture source, RenderTexture dest, int destIndex) + { + if (!GraphicsFormatUtility.IsAlphaOnlyFormat(source.graphicsFormat)) + { + Graphics.Blit(source, dest, 0, destIndex); + } + else + { + // Handle grayscale 2d cookie to RGB conversion + RenderTexture tempRT = new RenderTexture(AtlasSize, AtlasSize, 0, dest.format, 0); + tempRT.enableRandomWrite = true; + tempRT.Create(); + + using var cmd = new CommandBuffer(); + cmd.SetComputeIntParam(_blitGrayscaleCookieShader, Shader.PropertyToID("g_TextureWidth"), dest.width); + cmd.SetComputeIntParam(_blitGrayscaleCookieShader, Shader.PropertyToID("g_TextureHeight"), dest.height); + cmd.SetComputeTextureParam(_blitGrayscaleCookieShader, _blitGrayscaleCookieKernel, Shader.PropertyToID("g_Source"), source); + cmd.SetComputeTextureParam(_blitGrayscaleCookieShader, _blitGrayscaleCookieKernel, Shader.PropertyToID("g_Destination"), tempRT); + + cmd.DispatchCompute(_blitGrayscaleCookieShader, _blitGrayscaleCookieKernel, GraphicsHelpers.DivUp(dest.width, 8), GraphicsHelpers.DivUp(dest.height, 8), 1); + cmd.CopyTexture(tempRT, 0, dest, destIndex); + + Graphics.ExecuteCommandBuffer(cmd); + tempRT.Release(); + CoreUtils.Destroy(tempRT); + } + } + #endregion + + private void AddOrUpdateTexture(in World.MaterialDescriptor material, TextureType textureType, ref TextureSlotAllocator.TextureLocation location) + { + // Find appropriate allocator, scale, offset and texture + var allocator = _transmissionTextureAllocator; + Vector2 scale = material.TransmissionScale; + Vector2 offset = material.TransmissionOffset; + offset.y = -offset.y; // TODO: During extraction this value is negated. This should be fixed. Tracked here: https://jira.unity3d.com/browse/GFXFEAT-802 + Texture texture = material.Transmission; + if (textureType == TextureType.Albedo) + { + allocator = _albedoTextureAllocator; + texture = material.Albedo; + scale = material.AlbedoScale; + offset = material.AlbedoOffset; + } + else if (textureType == TextureType.Emission) + { + allocator = _emissionTextureAllocator; + texture = material.Emission; + scale = material.EmissionScale; + offset = material.EmissionOffset; + } + + // Invalid texture - bail + if (texture == null) + return; + + // Update path + if (location.IsValid) + { + allocator.UpdateTexture(in location, texture, scale, offset); + } + // Add path + else + { + location = allocator.AddTexture(texture, scale, offset); + } + } + + private void RemoveTextureIfPresent(TextureType textureType, ref TextureSlotAllocator.TextureLocation location) + { + if (!location.IsValid) + return; + + if (textureType == TextureType.Albedo) + _albedoTextureAllocator.RemoveTexture(in location); + else if (textureType == TextureType.Emission) + _emissionTextureAllocator.RemoveTexture(in location); + else + _transmissionTextureAllocator.RemoveTexture(in location); + + location = TextureSlotAllocator.TextureLocation.Invalid; + } + + private void FillAlbedoTextureAlphaWithOpacity(in World.MaterialDescriptor material, TextureSlotAllocator.TextureLocation location) + { + if (material.Transmission == null) + return; + + Vector2Int targetSize = _albedoTextureAllocator.GetTextureSize(location); + int targetOffsetX = location.TextureNode.PosX; + int targetOffsetY = location.TextureNode.PosY; + + using var cmd = new CommandBuffer(); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_TextureWidth"), targetSize.x); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_TextureHeight"), targetSize.y); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_TargetSlice"), location.AtlasIndex); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_TargetOffsetX"), targetOffsetX); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_TargetOffsetY"), targetOffsetY); + cmd.SetComputeFloatParam(_setAlphaChannelShader, Shader.PropertyToID("g_Alpha"), material.Alpha); + cmd.SetComputeFloatParam(_setAlphaChannelShader, Shader.PropertyToID("g_AlphaCutoff"), material.AlphaCutoff); + cmd.SetComputeIntParam(_setAlphaChannelShader, Shader.PropertyToID("g_UseAlphaCutoff"), material.UseAlphaCutoff ? 1 : 0); + cmd.SetComputeTextureParam(_setAlphaChannelShader, _setAlphaChannelKernel, Shader.PropertyToID("g_AlbedoTextures"), AlbedoTextures); + cmd.SetComputeTextureParam(_setAlphaChannelShader, _setAlphaChannelKernel, Shader.PropertyToID("g_OpacityTexture"), material.Transmission); + cmd.SetComputeVectorParam(_setAlphaChannelShader, Shader.PropertyToID("g_OpacityTextureUVTransform"), new float4(material.TransmissionScale, material.TransmissionOffset)); + + cmd.DispatchCompute(_setAlphaChannelShader, _setAlphaChannelKernel, GraphicsHelpers.DivUp(targetSize.x, _alphaShaderThreadGroupSizes.x), GraphicsHelpers.DivUp(targetSize.y, _alphaShaderThreadGroupSizes.y), 1); + Graphics.ExecuteCommandBuffer(cmd); + } + + // Render out an albedo/emission texture using the meta pass + private static Texture RenderGITexture(Material material, TextureType textureType) + { + if (textureType == TextureType.Transmission) + { + Debug.Assert(false, "Transmission textures should be handled outside of this function."); + return null; + } + + var targetTexture = CreateTexture(textureType); + + int metaPassIndex = material.FindPass("Meta"); + + var fragmentControl = Vector4.zero; + fragmentControl.x = textureType == TextureType.Albedo ? 1 : 0; + fragmentControl.y = textureType == TextureType.Emission ? 1 : 0; + + var properties = new MaterialPropertyBlock(); + properties.SetVector(Shader.PropertyToID("unity_MetaVertexControl"), new Vector4(0, 0, 0, 0)); + properties.SetVector(Shader.PropertyToID("unity_MetaFragmentControl"), fragmentControl); + properties.SetFloat(Shader.PropertyToID("unity_OneOverOutputBoost"), 1.0f); + properties.SetFloat(Shader.PropertyToID("unity_MaxOutputValue"), 0.97f); // only used by albedo pass + properties.SetInt(Shader.PropertyToID("unity_UseLinearSpace"), QualitySettings.activeColorSpace == ColorSpace.Linear ? 1 : 0); + material.DisableKeyword("EDITOR_VISUALIZATION"); + + using var cmd = new CommandBuffer(); + cmd.SetRenderTarget(targetTexture); + + cmd.SetViewProjectionMatrices(Matrix4x4.identity, GL.GetGPUProjectionMatrix(Matrix4x4.Ortho(-1, 1, -1, 1, -50, 50), true)); + cmd.SetViewport(new Rect(0, 0, targetTexture.width, targetTexture.height)); + cmd.ClearRenderTarget(false, true, new Color(0, 0, 0, 1)); + + if (_planeMesh == null) + _planeMesh = CreateQuadMesh(); + + if (metaPassIndex != -1) + cmd.DrawMesh(_planeMesh, Matrix4x4.identity, material, 0, metaPassIndex, properties); + + Graphics.ExecuteCommandBuffer(cmd); + return targetTexture; + } + + // The textures referenced by the material descriptor are owned by the material descriptor. + public static World.MaterialDescriptor ConvertUnityMaterialToMaterialDescriptor(Material material) + { + World.MaterialDescriptor descriptor = new(); + + // Emission + var emission = MaterialAspectOracle.GetEmission(material); + descriptor.EmissionType = emission.Type; + descriptor.EmissionColor = emission.Color; + if (emission.Type == MaterialPropertyType.Texture) + { + descriptor.Emission = RenderGITexture(material, TextureType.Emission); + descriptor.EmissionScale = Vector2.one; // Scale and offset handled by meta pass + descriptor.EmissionOffset = Vector2.zero; + } + + // Albedo + descriptor.Albedo = RenderGITexture(material, TextureType.Albedo); + descriptor.AlbedoScale = Vector2.one; // Scale and offset handled by meta pass + descriptor.AlbedoOffset = Vector2.zero; + + // Transmission + var transmission = MaterialAspectOracle.GetTransmission(material); + descriptor.TransmissionChannels = transmission.Channels; + descriptor.TransmissionScale = Vector2.one; // Scale and offset handled in the Blit below, so we don't have to apply it later + descriptor.TransmissionOffset = Vector2.zero; + if (transmission.Channels != TransmissionChannels.None) + { + RenderTexture prevRT = RenderTexture.active; + var transmissionTexture = CreateTexture(TextureType.Transmission); + Graphics.Blit(transmission.SourceTexture, transmissionTexture, transmission.Scale, transmission.Offset); + descriptor.Transmission = transmissionTexture; + RenderTexture.active = prevRT; + } + descriptor.Alpha = MaterialAspectOracle.GetAlpha(material); + descriptor.UseAlphaCutoff = MaterialAspectOracle.UsesAlphaCutoff(material); + descriptor.AlphaCutoff = MaterialAspectOracle.GetAlphaCutoff(material); + + // Other properties + descriptor.DoubleSidedGI = material.doubleSidedGI; + + return descriptor; + } + + static private GraphicsFormat GetTextureFormat(TextureType textureType) + { + switch (textureType) + { + case TextureType.Albedo: return GraphicsFormat.R8G8B8A8_UNorm; + case TextureType.Emission: return GraphicsFormat.R16G16B16A16_SFloat; // TODO(Yvain) might want to use B10G11R11_UFloatPack32 for emissionthere + case TextureType.Transmission: return GraphicsFormat.R8G8B8A8_UNorm; + case TextureType.LightCookie: return GraphicsFormat.R16G16B16A16_SFloat; // same as emission + case TextureType.LightCubemap: return GraphicsFormat.R16G16B16A16_SFloat; // same as emission + } + + return 0; + } + + private static RenderTexture CreateTextureArray(int sliceCount, TextureType textureType) + { + TextureDimension dimension = (textureType == TextureType.LightCubemap) ? TextureDimension.CubeArray: TextureDimension.Tex2DArray; + return CreateTexture(textureType, dimension, textureType == TextureType.LightCubemap ? sliceCount * 6 : sliceCount); + } + + private static RenderTexture CreateTexture(TextureType textureType, TextureDimension dimension = TextureDimension.Tex2D, int sliceCount = 1) + { + var format = GetTextureFormat(textureType); + + var texture = new RenderTexture(new RenderTextureDescriptor(AtlasSize, AtlasSize) + { + dimension = dimension, + depthBufferBits = 0, + volumeDepth = sliceCount, + msaaSamples = 1, + vrUsage = VRTextureUsage.OneEye, + graphicsFormat = format, + enableRandomWrite = true, + }) + { + name = "CreateTexture (MaterialPool)", + hideFlags = HideFlags.DontSaveInEditor, + wrapMode = TextureWrapMode.Repeat, + wrapModeU = TextureWrapMode.Repeat, + wrapModeV = TextureWrapMode.Repeat + }; + texture.Create(); + + return texture; + } + + private void RecreateMaterialList() + { + MaterialBuffer?.Dispose(); + MaterialBuffer = new ComputeBuffer(_materialSlotAllocator.capacity, Marshal.SizeOf()); + + var oldMaterialList = _materialList; + _materialList = new NativeArray(_materialSlotAllocator.capacity, Allocator.Persistent); + NativeArray.Copy(oldMaterialList, _materialList, oldMaterialList.Length); + oldMaterialList.Dispose(); + } + private static Mesh CreateQuadMesh() + { + Mesh mesh = new Mesh(); + + Vector3[] vertices = { + new(-1.0f, -1.0f, 0), + new(1.0f, -1.0f, 0), + new(-1.0f, 1.0f, 0), + new(1.0f, 1.0f, 0) + }; + mesh.vertices = vertices; + + Vector3[] normals = { + -Vector3.forward, + -Vector3.forward, + -Vector3.forward, + -Vector3.forward + }; + mesh.normals = normals; + + Vector2[] uv = { + new(0, 1), + new(1, 1), + new(0, 0), + new(1, 0) + }; + mesh.uv = uv; + mesh.uv2 = uv; + + int[] tris = { + 0, 2, 1, // lower left triangle + 2, 3, 1 // upper right triangle + }; + mesh.triangles = tris; + + mesh.hideFlags = HideFlags.DontSaveInEditor; + + return mesh; + } + } +} + + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs.meta new file mode 100644 index 00000000000..c5f56c37913 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/MaterialPool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e1da6470aa67d413da04758e62ce5af9 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes.meta new file mode 100644 index 00000000000..8d6cb9c634b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c46962a92697fa0448dfe0c7bb1d6cbb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh new file mode 100644 index 00000000000..9b1e58c35a5 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh @@ -0,0 +1,217 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!43 &4300000 +Mesh: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: 6FaceSkyboxMesh + serializedVersion: 11 + m_SubMeshes: + - serializedVersion: 2 + firstByte: 0 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 0 + vertexCount: 6 + localAABB: + m_Center: {x: 0, y: 0, z: 1} + m_Extent: {x: 1, y: 1, z: 0} + - serializedVersion: 2 + firstByte: 12 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 6 + vertexCount: 6 + localAABB: + m_Center: {x: 0, y: 0, z: -1} + m_Extent: {x: 1, y: 1, z: 0} + - serializedVersion: 2 + firstByte: 24 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 12 + vertexCount: 6 + localAABB: + m_Center: {x: 1, y: 0, z: 0} + m_Extent: {x: 0, y: 1, z: 1} + - serializedVersion: 2 + firstByte: 36 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 18 + vertexCount: 6 + localAABB: + m_Center: {x: -1, y: 0, z: 0} + m_Extent: {x: 0, y: 1, z: 1} + - serializedVersion: 2 + firstByte: 48 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 24 + vertexCount: 6 + localAABB: + m_Center: {x: 0, y: 1, z: 0} + m_Extent: {x: 1, y: 0, z: 1} + - serializedVersion: 2 + firstByte: 60 + indexCount: 6 + topology: 0 + baseVertex: 0 + firstVertex: 30 + vertexCount: 6 + localAABB: + m_Center: {x: 0, y: -1, z: 0} + m_Extent: {x: 1, y: 0, z: 1} + m_Shapes: + vertices: [] + shapes: [] + channels: [] + fullWeights: [] + m_BindPose: [] + m_BoneNameHashes: + m_RootBoneNameHash: 0 + m_BonesAABB: [] + m_VariableBoneCountWeights: + m_Data: + m_MeshCompression: 0 + m_IsReadable: 1 + m_KeepVertices: 1 + m_KeepIndices: 1 + m_IndexFormat: 0 + m_IndexBuffer: 00000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019001a001b001c001d001e001f002000210022002300 + m_VertexData: + serializedVersion: 3 + m_VertexCount: 36 + m_Channels: + - stream: 0 + offset: 0 + format: 0 + dimension: 3 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 12 + format: 0 + dimension: 2 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + m_DataSize: 720 + _typelessdata: 000080bf0000803f0000803f000000000000803f0000803f0000803f0000803f0000803f0000803f0000803f000080bf0000803f0000803f00000000000080bf0000803f0000803f000000000000803f0000803f000080bf0000803f0000803f00000000000080bf000080bf0000803f00000000000000000000803f0000803f000080bf000000000000803f000080bf0000803f000080bf0000803f0000803f000080bf000080bf000080bf0000803f000000000000803f0000803f000080bf000000000000803f000080bf000080bf000080bf0000803f000000000000803f000080bf000080bf00000000000000000000803f0000803f0000803f000000000000803f0000803f0000803f000080bf0000803f0000803f0000803f000080bf000080bf0000803f000000000000803f0000803f0000803f000000000000803f0000803f000080bf000080bf0000803f000000000000803f000080bf0000803f0000000000000000000080bf0000803f000080bf000000000000803f000080bf0000803f0000803f0000803f0000803f000080bf000080bf0000803f0000803f00000000000080bf0000803f000080bf000000000000803f000080bf000080bf0000803f0000803f00000000000080bf000080bf000080bf0000000000000000000080bf0000803f000080bf000000000000803f0000803f0000803f000080bf0000803f0000803f0000803f0000803f0000803f0000803f00000000000080bf0000803f000080bf000000000000803f0000803f0000803f0000803f0000803f00000000000080bf0000803f0000803f0000000000000000000080bf000080bf0000803f000000000000803f0000803f000080bf0000803f0000803f0000803f0000803f000080bf000080bf0000803f00000000000080bf000080bf0000803f000000000000803f0000803f000080bf000080bf0000803f00000000000080bf000080bf000080bf0000000000000000 + m_CompressedMesh: + m_Vertices: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_UV: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Normals: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Tangents: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Weights: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_NormalSigns: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_TangentSigns: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_FloatColors: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_BoneIndices: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_Triangles: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_UVInfo: 0 + m_LocalAABB: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 1, y: 1, z: 1} + m_MeshUsageFlags: 0 + m_CookingOptions: 30 + m_BakedConvexCollisionMesh: + m_BakedTriangleCollisionMesh: + m_MeshMetrics[0]: 1 + m_MeshMetrics[1]: 1 + m_MeshOptimizationFlags: 1 + m_StreamData: + serializedVersion: 2 + offset: 0 + size: 0 + path: diff --git a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters/TestCaseFilters.asset.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh.meta similarity index 64% rename from Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters/TestCaseFilters.asset.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh.meta index e021d76549d..b0110dd0270 100644 --- a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Lighting/Assets/Test/TestFilters/TestCaseFilters.asset.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: c5f675aa74d26584ca7f5db2da8a25af +guid: a80925ceebd011741b42509226cefc74 NativeFormatImporter: externalObjects: {} - mainObjectFileID: 0 + mainObjectFileID: 4300000 userData: assetBundleName: assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh new file mode 100644 index 00000000000..711033eff07 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh @@ -0,0 +1,167 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!43 &4300000 +Mesh: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: SkyboxMesh + serializedVersion: 11 + m_SubMeshes: + - serializedVersion: 2 + firstByte: 0 + indexCount: 5040 + topology: 0 + baseVertex: 0 + firstVertex: 0 + vertexCount: 5040 + localAABB: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 1, y: 1, z: 1} + m_Shapes: + vertices: [] + shapes: [] + channels: [] + fullWeights: [] + m_BindPose: [] + m_BoneNameHashes: + m_RootBoneNameHash: 0 + m_BonesAABB: [] + m_VariableBoneCountWeights: + m_Data: + m_MeshCompression: 0 + m_IsReadable: 1 + m_KeepVertices: 0 + m_KeepIndices: 0 + m_IndexFormat: 0 + m_IndexBuffer: 00000100020003000400050006000700080009000a000b000c000d000e000f0010001100120013001400150016001700180019001a001b001c001d001e001f0020002100220023002400250026002700280029002a002b002c002d002e002f0030003100320033003400350036003700380039003a003b003c003d003e003f0040004100420043004400450046004700480049004a004b004c004d004e004f0050005100520053005400550056005700580059005a005b005c005d005e005f0060006100620063006400650066006700680069006a006b006c006d006e006f0070007100720073007400750076007700780079007a007b007c007d007e007f0080008100820083008400850086008700880089008a008b008c008d008e008f0090009100920093009400950096009700980099009a009b009c009d009e009f00a000a100a200a300a400a500a600a700a800a900aa00ab00ac00ad00ae00af00b000b100b200b300b400b500b600b700b800b900ba00bb00bc00bd00be00bf00c000c100c200c300c400c500c600c700c800c900ca00cb00cc00cd00ce00cf00d000d100d200d300d400d500d600d700d800d900da00db00dc00dd00de00df00e000e100e200e300e400e500e600e700e800e900ea00eb00ec00ed00ee00ef00f000f100f200f300f400f500f600f700f800f900fa00fb00fc00fd00fe00ff0000010101020103010401050106010701080109010a010b010c010d010e010f0110011101120113011401150116011701180119011a011b011c011d011e011f0120012101220123012401250126012701280129012a012b012c012d012e012f0130013101320133013401350136013701380139013a013b013c013d013e013f0140014101420143014401450146014701480149014a014b014c014d014e014f0150015101520153015401550156015701580159015a015b015c015d015e015f0160016101620163016401650166016701680169016a016b016c016d016e016f0170017101720173017401750176017701780179017a017b017c017d017e017f0180018101820183018401850186018701880189018a018b018c018d018e018f0190019101920193019401950196019701980199019a019b019c019d019e019f01a001a101a201a301a401a501a601a701a801a901aa01ab01ac01ad01ae01af01b001b101b201b301b401b501b601b701b801b901ba01bb01bc01bd01be01bf01c001c101c201c301c401c501c601c701c801c901ca01cb01cc01cd01ce01cf01d001d101d201d301d401d501d601d701d801d901da01db01dc01dd01de01df01e001e101e201e301e401e501e601e701e801e901ea01eb01ec01ed01ee01ef01f001f101f201f301f401f501f601f701f801f901fa01fb01fc01fd01fe01ff0100020102020203020402050206020702080209020a020b020c020d020e020f0210021102120213021402150216021702180219021a021b021c021d021e021f0220022102220223022402250226022702280229022a022b022c022d022e022f0230023102320233023402350236023702380239023a023b023c023d023e023f0240024102420243024402450246024702480249024a024b024c024d024e024f0250025102520253025402550256025702580259025a025b025c025d025e025f0260026102620263026402650266026702680269026a026b026c026d026e026f0270027102720273027402750276027702780279027a027b027c027d027e027f0280028102820283028402850286028702880289028a028b028c028d028e028f0290029102920293029402950296029702980299029a029b029c029d029e029f02a002a102a202a302a402a502a602a702a802a902aa02ab02ac02ad02ae02af02b002b102b202b302b402b502b602b702b802b902ba02bb02bc02bd02be02bf02c002c102c202c302c402c502c602c702c802c902ca02cb02cc02cd02ce02cf02d002d102d202d302d402d502d602d702d802d902da02db02dc02dd02de02df02e002e102e202e302e402e502e602e702e802e902ea02eb02ec02ed02ee02ef02f002f102f202f302f402f502f602f702f802f902fa02fb02fc02fd02fe02ff0200030103020303030403050306030703080309030a030b030c030d030e030f0310031103120313031403150316031703180319031a031b031c031d031e031f0320032103220323032403250326032703280329032a032b032c032d032e032f0330033103320333033403350336033703380339033a033b033c033d033e033f0340034103420343034403450346034703480349034a034b034c034d034e034f0350035103520353035403550356035703580359035a035b035c035d035e035f0360036103620363036403650366036703680369036a036b036c036d036e036f0370037103720373037403750376037703780379037a037b037c037d037e037f0380038103820383038403850386038703880389038a038b038c038d038e038f0390039103920393039403950396039703980399039a039b039c039d039e039f03a003a103a203a303a403a503a603a703a803a903aa03ab03ac03ad03ae03af03b003b103b203b303b403b503b603b703b803b903ba03bb03bc03bd03be03bf03c003c103c203c303c403c503c603c703c803c903ca03cb03cc03cd03ce03cf03d003d103d203d303d403d503d603d703d803d903da03db03dc03dd03de03df03e003e103e203e303e403e503e603e703e803e903ea03eb03ec03ed03ee03ef03f003f103f203f303f403f503f603f703f803f903fa03fb03fc03fd03fe03ff0300040104020403040404050406040704080409040a040b040c040d040e040f0410041104120413041404150416041704180419041a041b041c041d041e041f0420042104220423042404250426042704280429042a042b042c042d042e042f0430043104320433043404350436043704380439043a043b043c043d043e043f0440044104420443044404450446044704480449044a044b044c044d044e044f0450045104520453045404550456045704580459045a045b045c045d045e045f0460046104620463046404650466046704680469046a046b046c046d046e046f0470047104720473047404750476047704780479047a047b047c047d047e047f0480048104820483048404850486048704880489048a048b048c048d048e048f0490049104920493049404950496049704980499049a049b049c049d049e049f04a004a104a204a304a404a504a604a704a804a904aa04ab04ac04ad04ae04af04b004b104b204b304b404b504b604b704b804b904ba04bb04bc04bd04be04bf04c004c104c204c304c404c504c604c704c804c904ca04cb04cc04cd04ce04cf04d004d104d204d304d404d504d604d704d804d904da04db04dc04dd04de04df04e004e104e204e304e404e504e604e704e804e904ea04eb04ec04ed04ee04ef04f004f104f204f304f404f504f604f704f804f904fa04fb04fc04fd04fe04ff0400050105020503050405050506050705080509050a050b050c050d050e050f0510051105120513051405150516051705180519051a051b051c051d051e051f0520052105220523052405250526052705280529052a052b052c052d052e052f0530053105320533053405350536053705380539053a053b053c053d053e053f0540054105420543054405450546054705480549054a054b054c054d054e054f0550055105520553055405550556055705580559055a055b055c055d055e055f0560056105620563056405650566056705680569056a056b056c056d056e056f0570057105720573057405750576057705780579057a057b057c057d057e057f0580058105820583058405850586058705880589058a058b058c058d058e058f0590059105920593059405950596059705980599059a059b059c059d059e059f05a005a105a205a305a405a505a605a705a805a905aa05ab05ac05ad05ae05af05b005b105b205b305b405b505b605b705b805b905ba05bb05bc05bd05be05bf05c005c105c205c305c405c505c605c705c805c905ca05cb05cc05cd05ce05cf05d005d105d205d305d405d505d605d705d805d905da05db05dc05dd05de05df05e005e105e205e305e405e505e605e705e805e905ea05eb05ec05ed05ee05ef05f005f105f205f305f405f505f605f705f805f905fa05fb05fc05fd05fe05ff0500060106020603060406050606060706080609060a060b060c060d060e060f0610061106120613061406150616061706180619061a061b061c061d061e061f0620062106220623062406250626062706280629062a062b062c062d062e062f0630063106320633063406350636063706380639063a063b063c063d063e063f0640064106420643064406450646064706480649064a064b064c064d064e064f0650065106520653065406550656065706580659065a065b065c065d065e065f0660066106620663066406650666066706680669066a066b066c066d066e066f0670067106720673067406750676067706780679067a067b067c067d067e067f0680068106820683068406850686068706880689068a068b068c068d068e068f0690069106920693069406950696069706980699069a069b069c069d069e069f06a006a106a206a306a406a506a606a706a806a906aa06ab06ac06ad06ae06af06b006b106b206b306b406b506b606b706b806b906ba06bb06bc06bd06be06bf06c006c106c206c306c406c506c606c706c806c906ca06cb06cc06cd06ce06cf06d006d106d206d306d406d506d606d706d806d906da06db06dc06dd06de06df06e006e106e206e306e406e506e606e706e806e906ea06eb06ec06ed06ee06ef06f006f106f206f306f406f506f606f706f806f906fa06fb06fc06fd06fe06ff0600070107020703070407050706070707080709070a070b070c070d070e070f0710071107120713071407150716071707180719071a071b071c071d071e071f0720072107220723072407250726072707280729072a072b072c072d072e072f0730073107320733073407350736073707380739073a073b073c073d073e073f0740074107420743074407450746074707480749074a074b074c074d074e074f0750075107520753075407550756075707580759075a075b075c075d075e075f0760076107620763076407650766076707680769076a076b076c076d076e076f0770077107720773077407750776077707780779077a077b077c077d077e077f0780078107820783078407850786078707880789078a078b078c078d078e078f0790079107920793079407950796079707980799079a079b079c079d079e079f07a007a107a207a307a407a507a607a707a807a907aa07ab07ac07ad07ae07af07b007b107b207b307b407b507b607b707b807b907ba07bb07bc07bd07be07bf07c007c107c207c307c407c507c607c707c807c907ca07cb07cc07cd07ce07cf07d007d107d207d307d407d507d607d707d807d907da07db07dc07dd07de07df07e007e107e207e307e407e507e607e707e807e907ea07eb07ec07ed07ee07ef07f007f107f207f307f407f507f607f707f807f907fa07fb07fc07fd07fe07ff0700080108020803080408050806080708080809080a080b080c080d080e080f0810081108120813081408150816081708180819081a081b081c081d081e081f0820082108220823082408250826082708280829082a082b082c082d082e082f0830083108320833083408350836083708380839083a083b083c083d083e083f0840084108420843084408450846084708480849084a084b084c084d084e084f0850085108520853085408550856085708580859085a085b085c085d085e085f0860086108620863086408650866086708680869086a086b086c086d086e086f0870087108720873087408750876087708780879087a087b087c087d087e087f0880088108820883088408850886088708880889088a088b088c088d088e088f0890089108920893089408950896089708980899089a089b089c089d089e089f08a008a108a208a308a408a508a608a708a808a908aa08ab08ac08ad08ae08af08b008b108b208b308b408b508b608b708b808b908ba08bb08bc08bd08be08bf08c008c108c208c308c408c508c608c708c808c908ca08cb08cc08cd08ce08cf08d008d108d208d308d408d508d608d708d808d908da08db08dc08dd08de08df08e008e108e208e308e408e508e608e708e808e908ea08eb08ec08ed08ee08ef08f008f108f208f308f408f508f608f708f808f908fa08fb08fc08fd08fe08ff0800090109020903090409050906090709080909090a090b090c090d090e090f0910091109120913091409150916091709180919091a091b091c091d091e091f0920092109220923092409250926092709280929092a092b092c092d092e092f0930093109320933093409350936093709380939093a093b093c093d093e093f0940094109420943094409450946094709480949094a094b094c094d094e094f0950095109520953095409550956095709580959095a095b095c095d095e095f0960096109620963096409650966096709680969096a096b096c096d096e096f0970097109720973097409750976097709780979097a097b097c097d097e097f0980098109820983098409850986098709880989098a098b098c098d098e098f0990099109920993099409950996099709980999099a099b099c099d099e099f09a009a109a209a309a409a509a609a709a809a909aa09ab09ac09ad09ae09af09b009b109b209b309b409b509b609b709b809b909ba09bb09bc09bd09be09bf09c009c109c209c309c409c509c609c709c809c909ca09cb09cc09cd09ce09cf09d009d109d209d309d409d509d609d709d809d909da09db09dc09dd09de09df09e009e109e209e309e409e509e609e709e809e909ea09eb09ec09ed09ee09ef09f009f109f209f309f409f509f609f709f809f909fa09fb09fc09fd09fe09ff09000a010a020a030a040a050a060a070a080a090a0a0a0b0a0c0a0d0a0e0a0f0a100a110a120a130a140a150a160a170a180a190a1a0a1b0a1c0a1d0a1e0a1f0a200a210a220a230a240a250a260a270a280a290a2a0a2b0a2c0a2d0a2e0a2f0a300a310a320a330a340a350a360a370a380a390a3a0a3b0a3c0a3d0a3e0a3f0a400a410a420a430a440a450a460a470a480a490a4a0a4b0a4c0a4d0a4e0a4f0a500a510a520a530a540a550a560a570a580a590a5a0a5b0a5c0a5d0a5e0a5f0a600a610a620a630a640a650a660a670a680a690a6a0a6b0a6c0a6d0a6e0a6f0a700a710a720a730a740a750a760a770a780a790a7a0a7b0a7c0a7d0a7e0a7f0a800a810a820a830a840a850a860a870a880a890a8a0a8b0a8c0a8d0a8e0a8f0a900a910a920a930a940a950a960a970a980a990a9a0a9b0a9c0a9d0a9e0a9f0aa00aa10aa20aa30aa40aa50aa60aa70aa80aa90aaa0aab0aac0aad0aae0aaf0ab00ab10ab20ab30ab40ab50ab60ab70ab80ab90aba0abb0abc0abd0abe0abf0ac00ac10ac20ac30ac40ac50ac60ac70ac80ac90aca0acb0acc0acd0ace0acf0ad00ad10ad20ad30ad40ad50ad60ad70ad80ad90ada0adb0adc0add0ade0adf0ae00ae10ae20ae30ae40ae50ae60ae70ae80ae90aea0aeb0aec0aed0aee0aef0af00af10af20af30af40af50af60af70af80af90afa0afb0afc0afd0afe0aff0a000b010b020b030b040b050b060b070b080b090b0a0b0b0b0c0b0d0b0e0b0f0b100b110b120b130b140b150b160b170b180b190b1a0b1b0b1c0b1d0b1e0b1f0b200b210b220b230b240b250b260b270b280b290b2a0b2b0b2c0b2d0b2e0b2f0b300b310b320b330b340b350b360b370b380b390b3a0b3b0b3c0b3d0b3e0b3f0b400b410b420b430b440b450b460b470b480b490b4a0b4b0b4c0b4d0b4e0b4f0b500b510b520b530b540b550b560b570b580b590b5a0b5b0b5c0b5d0b5e0b5f0b600b610b620b630b640b650b660b670b680b690b6a0b6b0b6c0b6d0b6e0b6f0b700b710b720b730b740b750b760b770b780b790b7a0b7b0b7c0b7d0b7e0b7f0b800b810b820b830b840b850b860b870b880b890b8a0b8b0b8c0b8d0b8e0b8f0b900b910b920b930b940b950b960b970b980b990b9a0b9b0b9c0b9d0b9e0b9f0ba00ba10ba20ba30ba40ba50ba60ba70ba80ba90baa0bab0bac0bad0bae0baf0bb00bb10bb20bb30bb40bb50bb60bb70bb80bb90bba0bbb0bbc0bbd0bbe0bbf0bc00bc10bc20bc30bc40bc50bc60bc70bc80bc90bca0bcb0bcc0bcd0bce0bcf0bd00bd10bd20bd30bd40bd50bd60bd70bd80bd90bda0bdb0bdc0bdd0bde0bdf0be00be10be20be30be40be50be60be70be80be90bea0beb0bec0bed0bee0bef0bf00bf10bf20bf30bf40bf50bf60bf70bf80bf90bfa0bfb0bfc0bfd0bfe0bff0b000c010c020c030c040c050c060c070c080c090c0a0c0b0c0c0c0d0c0e0c0f0c100c110c120c130c140c150c160c170c180c190c1a0c1b0c1c0c1d0c1e0c1f0c200c210c220c230c240c250c260c270c280c290c2a0c2b0c2c0c2d0c2e0c2f0c300c310c320c330c340c350c360c370c380c390c3a0c3b0c3c0c3d0c3e0c3f0c400c410c420c430c440c450c460c470c480c490c4a0c4b0c4c0c4d0c4e0c4f0c500c510c520c530c540c550c560c570c580c590c5a0c5b0c5c0c5d0c5e0c5f0c600c610c620c630c640c650c660c670c680c690c6a0c6b0c6c0c6d0c6e0c6f0c700c710c720c730c740c750c760c770c780c790c7a0c7b0c7c0c7d0c7e0c7f0c800c810c820c830c840c850c860c870c880c890c8a0c8b0c8c0c8d0c8e0c8f0c900c910c920c930c940c950c960c970c980c990c9a0c9b0c9c0c9d0c9e0c9f0ca00ca10ca20ca30ca40ca50ca60ca70ca80ca90caa0cab0cac0cad0cae0caf0cb00cb10cb20cb30cb40cb50cb60cb70cb80cb90cba0cbb0cbc0cbd0cbe0cbf0cc00cc10cc20cc30cc40cc50cc60cc70cc80cc90cca0ccb0ccc0ccd0cce0ccf0cd00cd10cd20cd30cd40cd50cd60cd70cd80cd90cda0cdb0cdc0cdd0cde0cdf0ce00ce10ce20ce30ce40ce50ce60ce70ce80ce90cea0ceb0cec0ced0cee0cef0cf00cf10cf20cf30cf40cf50cf60cf70cf80cf90cfa0cfb0cfc0cfd0cfe0cff0c000d010d020d030d040d050d060d070d080d090d0a0d0b0d0c0d0d0d0e0d0f0d100d110d120d130d140d150d160d170d180d190d1a0d1b0d1c0d1d0d1e0d1f0d200d210d220d230d240d250d260d270d280d290d2a0d2b0d2c0d2d0d2e0d2f0d300d310d320d330d340d350d360d370d380d390d3a0d3b0d3c0d3d0d3e0d3f0d400d410d420d430d440d450d460d470d480d490d4a0d4b0d4c0d4d0d4e0d4f0d500d510d520d530d540d550d560d570d580d590d5a0d5b0d5c0d5d0d5e0d5f0d600d610d620d630d640d650d660d670d680d690d6a0d6b0d6c0d6d0d6e0d6f0d700d710d720d730d740d750d760d770d780d790d7a0d7b0d7c0d7d0d7e0d7f0d800d810d820d830d840d850d860d870d880d890d8a0d8b0d8c0d8d0d8e0d8f0d900d910d920d930d940d950d960d970d980d990d9a0d9b0d9c0d9d0d9e0d9f0da00da10da20da30da40da50da60da70da80da90daa0dab0dac0dad0dae0daf0db00db10db20db30db40db50db60db70db80db90dba0dbb0dbc0dbd0dbe0dbf0dc00dc10dc20dc30dc40dc50dc60dc70dc80dc90dca0dcb0dcc0dcd0dce0dcf0dd00dd10dd20dd30dd40dd50dd60dd70dd80dd90dda0ddb0ddc0ddd0dde0ddf0de00de10de20de30de40de50de60de70de80de90dea0deb0dec0ded0dee0def0df00df10df20df30df40df50df60df70df80df90dfa0dfb0dfc0dfd0dfe0dff0d000e010e020e030e040e050e060e070e080e090e0a0e0b0e0c0e0d0e0e0e0f0e100e110e120e130e140e150e160e170e180e190e1a0e1b0e1c0e1d0e1e0e1f0e200e210e220e230e240e250e260e270e280e290e2a0e2b0e2c0e2d0e2e0e2f0e300e310e320e330e340e350e360e370e380e390e3a0e3b0e3c0e3d0e3e0e3f0e400e410e420e430e440e450e460e470e480e490e4a0e4b0e4c0e4d0e4e0e4f0e500e510e520e530e540e550e560e570e580e590e5a0e5b0e5c0e5d0e5e0e5f0e600e610e620e630e640e650e660e670e680e690e6a0e6b0e6c0e6d0e6e0e6f0e700e710e720e730e740e750e760e770e780e790e7a0e7b0e7c0e7d0e7e0e7f0e800e810e820e830e840e850e860e870e880e890e8a0e8b0e8c0e8d0e8e0e8f0e900e910e920e930e940e950e960e970e980e990e9a0e9b0e9c0e9d0e9e0e9f0ea00ea10ea20ea30ea40ea50ea60ea70ea80ea90eaa0eab0eac0ead0eae0eaf0eb00eb10eb20eb30eb40eb50eb60eb70eb80eb90eba0ebb0ebc0ebd0ebe0ebf0ec00ec10ec20ec30ec40ec50ec60ec70ec80ec90eca0ecb0ecc0ecd0ece0ecf0ed00ed10ed20ed30ed40ed50ed60ed70ed80ed90eda0edb0edc0edd0ede0edf0ee00ee10ee20ee30ee40ee50ee60ee70ee80ee90eea0eeb0eec0eed0eee0eef0ef00ef10ef20ef30ef40ef50ef60ef70ef80ef90efa0efb0efc0efd0efe0eff0e000f010f020f030f040f050f060f070f080f090f0a0f0b0f0c0f0d0f0e0f0f0f100f110f120f130f140f150f160f170f180f190f1a0f1b0f1c0f1d0f1e0f1f0f200f210f220f230f240f250f260f270f280f290f2a0f2b0f2c0f2d0f2e0f2f0f300f310f320f330f340f350f360f370f380f390f3a0f3b0f3c0f3d0f3e0f3f0f400f410f420f430f440f450f460f470f480f490f4a0f4b0f4c0f4d0f4e0f4f0f500f510f520f530f540f550f560f570f580f590f5a0f5b0f5c0f5d0f5e0f5f0f600f610f620f630f640f650f660f670f680f690f6a0f6b0f6c0f6d0f6e0f6f0f700f710f720f730f740f750f760f770f780f790f7a0f7b0f7c0f7d0f7e0f7f0f800f810f820f830f840f850f860f870f880f890f8a0f8b0f8c0f8d0f8e0f8f0f900f910f920f930f940f950f960f970f980f990f9a0f9b0f9c0f9d0f9e0f9f0fa00fa10fa20fa30fa40fa50fa60fa70fa80fa90faa0fab0fac0fad0fae0faf0fb00fb10fb20fb30fb40fb50fb60fb70fb80fb90fba0fbb0fbc0fbd0fbe0fbf0fc00fc10fc20fc30fc40fc50fc60fc70fc80fc90fca0fcb0fcc0fcd0fce0fcf0fd00fd10fd20fd30fd40fd50fd60fd70fd80fd90fda0fdb0fdc0fdd0fde0fdf0fe00fe10fe20fe30fe40fe50fe60fe70fe80fe90fea0feb0fec0fed0fee0fef0ff00ff10ff20ff30ff40ff50ff60ff70ff80ff90ffa0ffb0ffc0ffd0ffe0fff0f00100110021003100410051006100710081009100a100b100c100d100e100f1010101110121013101410151016101710181019101a101b101c101d101e101f1020102110221023102410251026102710281029102a102b102c102d102e102f1030103110321033103410351036103710381039103a103b103c103d103e103f1040104110421043104410451046104710481049104a104b104c104d104e104f1050105110521053105410551056105710581059105a105b105c105d105e105f1060106110621063106410651066106710681069106a106b106c106d106e106f1070107110721073107410751076107710781079107a107b107c107d107e107f1080108110821083108410851086108710881089108a108b108c108d108e108f1090109110921093109410951096109710981099109a109b109c109d109e109f10a010a110a210a310a410a510a610a710a810a910aa10ab10ac10ad10ae10af10b010b110b210b310b410b510b610b710b810b910ba10bb10bc10bd10be10bf10c010c110c210c310c410c510c610c710c810c910ca10cb10cc10cd10ce10cf10d010d110d210d310d410d510d610d710d810d910da10db10dc10dd10de10df10e010e110e210e310e410e510e610e710e810e910ea10eb10ec10ed10ee10ef10f010f110f210f310f410f510f610f710f810f910fa10fb10fc10fd10fe10ff1000110111021103110411051106110711081109110a110b110c110d110e110f1110111111121113111411151116111711181119111a111b111c111d111e111f1120112111221123112411251126112711281129112a112b112c112d112e112f1130113111321133113411351136113711381139113a113b113c113d113e113f1140114111421143114411451146114711481149114a114b114c114d114e114f1150115111521153115411551156115711581159115a115b115c115d115e115f1160116111621163116411651166116711681169116a116b116c116d116e116f1170117111721173117411751176117711781179117a117b117c117d117e117f1180118111821183118411851186118711881189118a118b118c118d118e118f1190119111921193119411951196119711981199119a119b119c119d119e119f11a011a111a211a311a411a511a611a711a811a911aa11ab11ac11ad11ae11af11b011b111b211b311b411b511b611b711b811b911ba11bb11bc11bd11be11bf11c011c111c211c311c411c511c611c711c811c911ca11cb11cc11cd11ce11cf11d011d111d211d311d411d511d611d711d811d911da11db11dc11dd11de11df11e011e111e211e311e411e511e611e711e811e911ea11eb11ec11ed11ee11ef11f011f111f211f311f411f511f611f711f811f911fa11fb11fc11fd11fe11ff1100120112021203120412051206120712081209120a120b120c120d120e120f1210121112121213121412151216121712181219121a121b121c121d121e121f1220122112221223122412251226122712281229122a122b122c122d122e122f1230123112321233123412351236123712381239123a123b123c123d123e123f1240124112421243124412451246124712481249124a124b124c124d124e124f1250125112521253125412551256125712581259125a125b125c125d125e125f1260126112621263126412651266126712681269126a126b126c126d126e126f1270127112721273127412751276127712781279127a127b127c127d127e127f1280128112821283128412851286128712881289128a128b128c128d128e128f1290129112921293129412951296129712981299129a129b129c129d129e129f12a012a112a212a312a412a512a612a712a812a912aa12ab12ac12ad12ae12af12b012b112b212b312b412b512b612b712b812b912ba12bb12bc12bd12be12bf12c012c112c212c312c412c512c612c712c812c912ca12cb12cc12cd12ce12cf12d012d112d212d312d412d512d612d712d812d912da12db12dc12dd12de12df12e012e112e212e312e412e512e612e712e812e912ea12eb12ec12ed12ee12ef12f012f112f212f312f412f512f612f712f812f912fa12fb12fc12fd12fe12ff1200130113021303130413051306130713081309130a130b130c130d130e130f1310131113121313131413151316131713181319131a131b131c131d131e131f1320132113221323132413251326132713281329132a132b132c132d132e132f1330133113321333133413351336133713381339133a133b133c133d133e133f1340134113421343134413451346134713481349134a134b134c134d134e134f1350135113521353135413551356135713581359135a135b135c135d135e135f1360136113621363136413651366136713681369136a136b136c136d136e136f1370137113721373137413751376137713781379137a137b137c137d137e137f1380138113821383138413851386138713881389138a138b138c138d138e138f1390139113921393139413951396139713981399139a139b139c139d139e139f13a013a113a213a313a413a513a613a713a813a913aa13ab13ac13ad13ae13af13 + m_VertexData: + serializedVersion: 3 + m_VertexCount: 5040 + m_Channels: + - stream: 0 + offset: 0 + format: 0 + dimension: 3 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + - stream: 0 + offset: 0 + format: 0 + dimension: 0 + m_DataSize: 60480 + _typelessdata: 000000000000803f0000000000000000bf147b3fc2c547bec2c5473ebf147b3f0000000000000000bf147b3fc2c547be000000005e836c3f15efc3beea864b3ec8ad753fea864bbeea864b3ec8ad753fea864bbec2c5473ebf147b3f0000000000000000bf147b3fc2c547be15efc33e5e836c3f00000000c2c5473ebf147b3f00000000ea864b3ec8ad753fea864bbe000000005e836c3f15efc3be0000000031db543fda390ebfe3db553e04ec633fb429cfbe0000000031db543fda390ebf00000000f304353ff30435bf8c65583e9ee6493f3acd13bf8c65583e9ee6493f3acd13bfe3db553e04ec633fb429cfbe0000000031db543fda390ebfec05d13eec05513fec05d1bee3db553e04ec633fb429cfbe8c65583e9ee6493f3acd13bfec05d13eec05513fec05d1beb429cf3e04ec633fe3db55bee3db553e04ec633fb429cfbeb429cf3e04ec633fe3db55be15efc33e5e836c3f00000000ea864b3ec8ad753fea864bbeea864b3ec8ad753fea864bbee3db553e04ec633fb429cfbeb429cf3e04ec633fe3db55be000000005e836c3f15efc3bee3db553e04ec633fb429cfbeea864b3ec8ad753fea864bbef304353ff304353f00000000da390e3f31db543f0000000039cd133f9de6493f8b6558beda390e3f31db543f0000000015efc33e5e836c3f00000000b429cf3e04ec633fe3db55beb429cf3e04ec633fe3db55be39cd133f9de6493f8b6558beda390e3f31db543f00000000ec05d13eec05513fec05d1be39cd133f9de6493f8b6558beb429cf3e04ec633fe3db55be00000000f304353ff30435bf00000000da390e3f31db54bf8c65583e3acd133f9ee649bf00000000da390e3f31db54bf0000000015efc33e5e836cbfe3db553eb429cf3e04ec63bfe3db553eb429cf3e04ec63bf8c65583e3acd133f9ee649bf00000000da390e3f31db54bfec05d13eec05d13eec0551bf8c65583e3acd133f9ee649bfe3db553eb429cf3e04ec63bf0000000015efc33e5e836cbf0000000032a0943e0bfa74bfc873cd3db941963e6c6073bfc873cd3db941963e6c6073bf0000000032a0943e0bfa74bf00000000c2c5473ebf147bbfc873cd3db941963e6c6073bf00000000c2c5473ebf147bbfeb864b3eeb864b3ec9ad75bf00000000c2c5473ebf147bbf000000008340163eac3a7dbf4b2f4a3d0bac163ee9e57cbf4b2f4a3d0bac163ee9e57cbf000000008340163eac3a7dbf0000000035bdc83d6dc47ebf4b2f4a3d0bac163ee9e57cbf0000000035bdc83d6dc47ebf6fb3c93d6fb3c93d37817dbf0000000000000000000080bfb7314a3db7314a3d1b607fbf0000000030fb483d10b17fbfb7314a3db7314a3d1b607fbf6fb3c93d6fb3c93d37817dbf0000000035bdc83d6dc47ebf0000000030fb483d10b17fbfb7314a3db7314a3d1b607fbf0000000035bdc83d6dc47ebf6fb3c93d6fb3c93d37817dbfb7314a3db7314a3d1b607fbf0bac163e4b2f4a3de9e57cbfb7314a3db7314a3d1b607fbf0000000000000000000080bfc2c5473e00000000bf147bbf0bac163e4b2f4a3de9e57cbfb7314a3db7314a3d1b607fbfc2c5473e00000000bf147bbfc2c5473e00000000bf147bbf9278493ed7ce4c3d69ab7abf0bac163e4b2f4a3de9e57cbf0bac163e4b2f4a3de9e57cbf9278493ed7ce4c3d69ab7abf28aa4a3e288dcc3d4ba179bf0bac163e4b2f4a3de9e57cbf28aa4a3e288dcc3d4ba179bf6fb3c93d6fb3c93d37817dbfeb864b3eeb864b3ec9ad75bf3397183e3397183e3b407abfc1594b3ed917193e10f777bf3397183e3397183e3b407abf6fb3c93d6fb3c93d37817dbf28aa4a3e288dcc3d4ba179bfc1594b3ed917193e10f777bf3397183e3397183e3b407abf28aa4a3e288dcc3d4ba179bf6fb3c93d6fb3c93d37817dbf3397183e3397183e3b407abf4b2f4a3d0bac163ee9e57cbf3397183e3397183e3b407abfeb864b3eeb864b3ec9ad75bf00000000c2c5473ebf147bbf4b2f4a3d0bac163ee9e57cbf3397183e3397183e3b407abf00000000c2c5473ebf147bbfeb864b3eeb864b3ec9ad75bfc1594b3ed917193e10f777bfdb9c7c3e917c193e451a75bfdb9c7c3e917c193e451a75bfc1594b3ed917193e10f777bf28aa4a3e288dcc3d4ba179bfdb9c7c3e917c193e451a75bf28aa4a3e288dcc3d4ba179bfb941963ec873cd3d6c6073bfc2c5473e00000000bf147bbf85ca7a3e0efc4d3d92de77bf9278493ed7ce4c3d69ab7abf85ca7a3e0efc4d3d92de77bfb941963ec873cd3d6c6073bf28aa4a3e288dcc3d4ba179bf9278493ed7ce4c3d69ab7abf85ca7a3e0efc4d3d92de77bf28aa4a3e288dcc3d4ba179bfb941963ec873cd3d6c6073bf85ca7a3e0efc4d3d92de77bf9480ad3e6fef4d3d4e8270bf85ca7a3e0efc4d3d92de77bfc2c5473e00000000bf147bbf15efc33e000000005e836cbf9480ad3e6fef4d3d4e8270bf85ca7a3e0efc4d3d92de77bf15efc33e000000005e836cbf15efc33e000000005e836cbf4a99c73e3a62573d745c6bbf9480ad3e6fef4d3d4e8270bf9480ad3e6fef4d3d4e8270bf4a99c73e3a62573d745c6bbf90b2ca3e0714d73da18a69bf9480ad3e6fef4d3d4e8270bf90b2ca3e0714d73da18a69bfb941963ec873cd3d6c6073bfb429cf3ee3db553e04ec63bf6255b33e52d81e3ec4786cbfa538cd3e61ed203e370f67bf6255b33e52d81e3ec4786cbfb941963ec873cd3d6c6073bf90b2ca3e0714d73da18a69bfa538cd3e61ed203e370f67bf6255b33e52d81e3ec4786cbf90b2ca3e0714d73da18a69bfb941963ec873cd3d6c6073bf6255b33e52d81e3ec4786cbfdb9c7c3e917c193e451a75bf6255b33e52d81e3ec4786cbfb429cf3ee3db553e04ec63bfeb864b3eeb864b3ec9ad75bfdb9c7c3e917c193e451a75bf6255b33e52d81e3ec4786cbfeb864b3eeb864b3ec9ad75bfb429cf3ee3db553e04ec63bf8947d13e82e09e3ed7b75bbfa8929e3ea8929e3e3b2366bfa8929e3ea8929e3e3b2366bf8947d13e82e09e3ed7b75bbfec05d13eec05d13eec0551bfa8929e3ea8929e3e3b2366bfec05d13eec05d13eec0551bfe3db553eb429cf3e04ec63bfe3db553eb429cf3e04ec63bf0cf0513e6d629b3e76366ebfa8929e3ea8929e3e3b2366bfa8929e3ea8929e3e3b2366bf0cf0513e6d629b3e76366ebfeb864b3eeb864b3ec9ad75bfa8929e3ea8929e3e3b2366bfeb864b3eeb864b3ec9ad75bfb429cf3ee3db553e04ec63bfeb864b3eeb864b3ec9ad75bf0cf0513e6d629b3e76366ebfc873cd3db941963e6c6073bfc873cd3db941963e6c6073bf0cf0513e6d629b3e76366ebfe3db553eb429cf3e04ec63bfc873cd3db941963e6c6073bfe3db553eb429cf3e04ec63bf0000000015efc33e5e836cbf3acd133f8c65583e9ee649bfc50d133f99db223e7d8e4dbfe2261d3f111d233e28ee45bfe2261d3f111d233e28ee45bfc50d133f99db223e7d8e4dbf69df113fa6add93d569b50bfe2261d3f111d233e28ee45bf69df113fa6add93d569b50bf46d4253f9243da3db91c41bfda390e3f0000000031db54bfe2861a3fb2f85a3d67a44bbf0843103fdaff593dd80a53bfe2861a3fb2f85a3d67a44bbf46d4253f9243da3db91c41bf69df113fa6add93d569b50bf0843103fdaff593dd80a53bfe2861a3fb2f85a3d67a44bbf69df113fa6add93d569b50bf46d4253f9243da3db91c41bfe2861a3fb2f85a3d67a44bbfdecb2d3f76bb5a3d97773bbfe2861a3fb2f85a3d67a44bbfda390e3f0000000031db54bff304353f00000000f30435bfdecb2d3f76bb5a3d97773bbfe2861a3fb2f85a3d67a44bbff304353f00000000f30435bfec05d13eec05d13eec0551bf8947d13e82e09e3ed7b75bbf8d7dfe3eb8fa9f3e233c4fbf8d7dfe3eb8fa9f3e233c4fbf8947d13e82e09e3ed7b75bbfb429cf3ee3db553e04ec63bf8d7dfe3eb8fa9f3e233c4fbfb429cf3ee3db553e04ec63bf3acd133f8c65583e9ee649bfda390e3f0000000031db54bf0843103fdaff593dd80a53bfcc66053fe839583d9b135abfcc66053fe839583d9b135abf0843103fdaff593dd80a53bf69df113fa6add93d569b50bfcc66053fe839583d9b135abf69df113fa6add93d569b50bf94fbf73e62c0d73de4565ebf3acd133f8c65583e9ee649bf4158083fc5ab223edcd154bfc50d133f99db223e7d8e4dbf4158083fc5ab223edcd154bf94fbf73e62c0d73de4565ebf69df113fa6add93d569b50bfc50d133f99db223e7d8e4dbf4158083fc5ab223edcd154bf69df113fa6add93d569b50bf94fbf73e62c0d73de4565ebf4158083fc5ab223edcd154bfd212e43ea638213e42a061bf4158083fc5ab223edcd154bf3acd133f8c65583e9ee649bfb429cf3ee3db553e04ec63bfd212e43ea638213e42a061bf4158083fc5ab223edcd154bfb429cf3ee3db553e04ec63bfb429cf3ee3db553e04ec63bfa538cd3e61ed203e370f67bfd212e43ea638213e42a061bfd212e43ea638213e42a061bfa538cd3e61ed203e370f67bf90b2ca3e0714d73da18a69bfd212e43ea638213e42a061bf90b2ca3e0714d73da18a69bf94fbf73e62c0d73de4565ebf15efc33e000000005e836cbf22a4de3e4d6a583dd12166bf4a99c73e3a62573d745c6bbf22a4de3e4d6a583dd12166bf94fbf73e62c0d73de4565ebf90b2ca3e0714d73da18a69bf4a99c73e3a62573d745c6bbf22a4de3e4d6a583dd12166bf90b2ca3e0714d73da18a69bf94fbf73e62c0d73de4565ebf22a4de3e4d6a583dd12166bfcc66053fe839583d9b135abf22a4de3e4d6a583dd12166bf15efc33e000000005e836cbfda390e3f0000000031db54bfcc66053fe839583d9b135abf22a4de3e4d6a583dd12166bfda390e3f0000000031db54bff304353f00000000f30435bf97773b3f76bb5a3ddecb2dbfdecb2d3f76bb5a3d97773bbf97773b3f76bb5a3ddecb2dbfb91c413f9243da3d46d425bf46d4253f9243da3db91c41bfdecb2d3f76bb5a3d97773bbf97773b3f76bb5a3ddecb2dbf46d4253f9243da3db91c41bf9ee6493f8c65583e3acd13bf744f393f350b243e00cc2bbf28ee453f111d233ee2261dbf744f393f350b243e00cc2bbf46d4253f9243da3db91c41bfb91c413f9243da3d46d425bf28ee453f111d233ee2261dbf744f393f350b243e00cc2bbfb91c413f9243da3d46d425bf46d4253f9243da3db91c41bf744f393f350b243e00cc2bbfe2261d3f111d233e28ee45bf744f393f350b243e00cc2bbf9ee6493f8c65583e3acd13bf3acd133f8c65583e9ee649bfe2261d3f111d233e28ee45bf744f393f350b243e00cc2bbf3acd133f8c65583e9ee649bf9ee6493f8c65583e3acd13bf233c4f3fb8fa9f3e8d7dfebeeb7e383f3fc1a43ebb321dbfeb7e383f3fc1a43ebb321dbf233c4f3fb8fa9f3e8d7dfebeec05513fec05d13eec05d1beeb7e383f3fc1a43ebb321dbfec05513fec05d13eec05d1bef7bc233f4a51da3ef7bc23bff7bc233f4a51da3ef7bc23bfbb321d3f3fc1a43eeb7e38bfeb7e383f3fc1a43ebb321dbfeb7e383f3fc1a43ebb321dbfbb321d3f3fc1a43eeb7e38bf3acd133f8c65583e9ee649bfeb7e383f3fc1a43ebb321dbf3acd133f8c65583e9ee649bf9ee6493f8c65583e3acd13bf3acd133f8c65583e9ee649bfbb321d3f3fc1a43eeb7e38bf8d7dfe3eb8fa9f3e233c4fbfbb321d3f3fc1a43eeb7e38bff7bc233f4a51da3ef7bc23bfec05d13eec05d13eec0551bf8d7dfe3eb8fa9f3e233c4fbfbb321d3f3fc1a43eeb7e38bfec05d13eec05d13eec0551bfec05513fec05d13eec05d1be9de6493f39cd133f8b6558bef7bc233ff7bc233f4a51dabe9de6493f39cd133f8b6558bef304353ff304353f0000000039cd133f9de6493f8b6558be39cd133f9de6493f8b6558bef7bc233ff7bc233f4a51dabe9de6493f39cd133f8b6558beec05d13eec05513fec05d1bef7bc233ff7bc233f4a51dabe39cd133f9de6493f8b6558beec05d13eec05513fec05d1be4a51da3ef7bc233ff7bc23bff7bc233ff7bc233f4a51dabe4a51da3ef7bc233ff7bc23bfec05d13eec05d13eec0551bff7bc233f4a51da3ef7bc23bff7bc233f4a51da3ef7bc23bff7bc233ff7bc233f4a51dabe4a51da3ef7bc233ff7bc23bfec05513fec05d13eec05d1bef7bc233ff7bc233f4a51dabef7bc233f4a51da3ef7bc23bf00000000f304353ff30435bf8c65583e3acd133f9ee649bf8c65583e9ee6493f3acd13bf8c65583e3acd133f9ee649bfec05d13eec05d13eec0551bf4a51da3ef7bc233ff7bc23bf4a51da3ef7bc233ff7bc23bf8c65583e9ee6493f3acd13bf8c65583e3acd133f9ee649bfec05d13eec05513fec05d1be8c65583e9ee6493f3acd13bf4a51da3ef7bc233ff7bc23bfbf147b3fc2c5473e00000000e9e57c3f0bac163e4b2f4abdac3a7d3f8340163e00000000e9e57c3f0bac163e4b2f4abd37817d3f6fb3c93d6fb3c9bd6dc47e3f35bdc83d00000000ac3a7d3f8340163e00000000e9e57c3f0bac163e4b2f4abd6dc47e3f35bdc83d0000000037817d3f6fb3c93d6fb3c9bde9e57c3f4b2f4a3d0bac16be1c607f3fb8314a3db8314abd1c607f3fb8314a3db8314abde9e57c3f4b2f4a3d0bac16bebf147b3f00000000c2c547be1c607f3fb8314a3db8314abdbf147b3f00000000c2c547be0000803f00000000000000000000803f000000000000000010b17f3f30fb483d000000001c607f3fb8314a3db8314abd1c607f3fb8314a3db8314abd10b17f3f30fb483d000000006dc47e3f35bdc83d000000001c607f3fb8314a3db8314abd6dc47e3f35bdc83d0000000037817d3f6fb3c93d6fb3c9bd5e836c3f15efc33e000000006c60733fba41963ec873cdbd0bfa743f32a0943e000000006c60733fba41963ec873cdbdc8ad753fea864b3eea864bbebf147b3fc2c5473e000000000bfa743f32a0943e000000006c60733fba41963ec873cdbdbf147b3fc2c5473e00000000bf147b3f00000000c2c547bee9e57c3f4b2f4a3d0bac16be69ab7a3fd6ce4c3d927849bee9e57c3f4b2f4a3d0bac16be37817d3f6fb3c93d6fb3c9bd4ba1793f278dcc3d28aa4abe69ab7a3fd6ce4c3d927849bee9e57c3f4b2f4a3d0bac16be4ba1793f278dcc3d28aa4abe37817d3f6fb3c93d6fb3c9bde9e57c3f0bac163e4b2f4abd3b407a3f3397183e339718be3b407a3f3397183e339718bee9e57c3f0bac163e4b2f4abdbf147b3fc2c5473e000000003b407a3f3397183e339718bebf147b3fc2c5473e00000000c8ad753fea864b3eea864bbec8ad753fea864b3eea864bbe10f7773fd817193ec0594bbe3b407a3f3397183e339718be3b407a3f3397183e339718be10f7773fd817193ec0594bbe4ba1793f278dcc3d28aa4abe3b407a3f3397183e339718be4ba1793f278dcc3d28aa4abe37817d3f6fb3c93d6fb3c9bdc8ad753fea864b3eea864bbe451a753f907c193edc9c7cbe10f7773fd817193ec0594bbe451a753f907c193edc9c7cbe6c60733fc873cd3dba4196be4ba1793f278dcc3d28aa4abe10f7773fd817193ec0594bbe451a753f907c193edc9c7cbe4ba1793f278dcc3d28aa4abe6c60733fc873cd3dba4196be4e82703f6fef4d3d9580adbe92de773f0efc4d3d86ca7abe92de773f0efc4d3d86ca7abe4e82703f6fef4d3d9580adbe5e836c3f0000000015efc3be92de773f0efc4d3d86ca7abe5e836c3f0000000015efc3bebf147b3f00000000c2c547bebf147b3f00000000c2c547be69ab7a3fd6ce4c3d927849be92de773f0efc4d3d86ca7abe92de773f0efc4d3d86ca7abe69ab7a3fd6ce4c3d927849be4ba1793f278dcc3d28aa4abe92de773f0efc4d3d86ca7abe4ba1793f278dcc3d28aa4abe6c60733fc873cd3dba4196be5e836c3f15efc33e0000000031db543fda390e3f0000000004ec633fb429cf3ee3db55be31db543fda390e3f00000000f304353ff304353f000000009de6493f39cd133f8b6558be9de6493f39cd133f8b6558be04ec633fb429cf3ee3db55be31db543fda390e3f00000000ec05513fec05d13eec05d1be04ec633fb429cf3ee3db55be9de6493f39cd133f8b6558be04ec633fe3db553eb429cfbe3b23663fa8929e3ea8929ebed7b75b3f82e09e3e8947d1be3b23663fa8929e3ea8929ebe04ec633fb429cf3ee3db55beec05513fec05d13eec05d1bed7b75b3f82e09e3e8947d1be3b23663fa8929e3ea8929ebeec05513fec05d13eec05d1be5e836c3f0000000015efc3be4e82703f6fef4d3d9580adbe745c6b3f3a62573d4a99c7be4e82703f6fef4d3d9580adbe6c60733fc873cd3dba4196bea18a693f0714d73d90b2cabe745c6b3f3a62573d4a99c7be4e82703f6fef4d3d9580adbea18a693f0714d73d90b2cabe6c60733fc873cd3dba4196be451a753f907c193edc9c7cbec4786c3f52d81e3e6355b3bec4786c3f52d81e3e6355b3be451a753f907c193edc9c7cbec8ad753fea864b3eea864bbec4786c3f52d81e3e6355b3bec8ad753fea864b3eea864bbe04ec633fe3db553eb429cfbe04ec633fe3db553eb429cfbe370f673f61ed203ea538cdbec4786c3f52d81e3e6355b3bec4786c3f52d81e3e6355b3be370f673f61ed203ea538cdbea18a693f0714d73d90b2cabec4786c3f52d81e3e6355b3bea18a693f0714d73d90b2cabe6c60733fc873cd3dba4196be04ec633fb429cf3ee3db55be3b23663fa8929e3ea8929ebe76366e3f6c629b3e0bf051be3b23663fa8929e3ea8929ebe04ec633fe3db553eb429cfbec8ad753fea864b3eea864bbe76366e3f6c629b3e0bf051be3b23663fa8929e3ea8929ebec8ad753fea864b3eea864bbec8ad753fea864b3eea864bbe6c60733fba41963ec873cdbd76366e3f6c629b3e0bf051be6c60733fba41963ec873cdbd5e836c3f15efc33e0000000004ec633fb429cf3ee3db55be76366e3f6c629b3e0bf051be6c60733fba41963ec873cdbd04ec633fb429cf3ee3db55be9ee6493f8c65583e3acd13bf28ee453f111d233ee2261dbf7d8e4d3f9adb223ec60d13bf28ee453f111d233ee2261dbfb91c413f9243da3d46d425bf569b503fa6add93d69df11bf7d8e4d3f9adb223ec60d13bf28ee453f111d233ee2261dbf569b503fa6add93d69df11bfb91c413f9243da3d46d425bf97773b3f76bb5a3ddecb2dbf67a44b3fb2f85a3de2861abf67a44b3fb2f85a3de2861abf97773b3f76bb5a3ddecb2dbff304353f00000000f30435bf67a44b3fb2f85a3de2861abff304353f00000000f30435bf31db543f00000000da390ebf31db543f00000000da390ebfd80a533fdaff593d084310bf67a44b3fb2f85a3de2861abf67a44b3fb2f85a3de2861abfd80a533fdaff593d084310bf569b503fa6add93d69df11bf67a44b3fb2f85a3de2861abf569b503fa6add93d69df11bfb91c413f9243da3d46d425bf04ec633fe3db553eb429cfbe42a0613fa638213ed212e4be370f673f61ed203ea538cdbe42a0613fa638213ed212e4bee4565e3f62c0d73d94fbf7bea18a693f0714d73d90b2cabe370f673f61ed203ea538cdbe42a0613fa638213ed212e4bea18a693f0714d73d90b2cabee4565e3f62c0d73d94fbf7be9b135a3fe839583dcc6605bfd121663f4d6a583d22a4debed121663f4d6a583d22a4debe9b135a3fe839583dcc6605bf31db543f00000000da390ebfd121663f4d6a583d22a4debe31db543f00000000da390ebf5e836c3f0000000015efc3be5e836c3f0000000015efc3be745c6b3f3a62573d4a99c7bed121663f4d6a583d22a4debed121663f4d6a583d22a4debe745c6b3f3a62573d4a99c7bea18a693f0714d73d90b2cabed121663f4d6a583d22a4debea18a693f0714d73d90b2cabee4565e3f62c0d73d94fbf7be31db543f00000000da390ebf9b135a3fe839583dcc6605bfd80a533fdaff593d084310bf9b135a3fe839583dcc6605bfe4565e3f62c0d73d94fbf7be569b503fa6add93d69df11bfd80a533fdaff593d084310bf9b135a3fe839583dcc6605bf569b503fa6add93d69df11bfe4565e3f62c0d73d94fbf7be42a0613fa638213ed212e4bedcd1543fc5ab223e415808bfdcd1543fc5ab223e415808bf42a0613fa638213ed212e4be04ec633fe3db553eb429cfbedcd1543fc5ab223e415808bf04ec633fe3db553eb429cfbe9ee6493f8c65583e3acd13bf9ee6493f8c65583e3acd13bf7d8e4d3f9adb223ec60d13bfdcd1543fc5ab223e415808bfdcd1543fc5ab223e415808bf7d8e4d3f9adb223ec60d13bf569b503fa6add93d69df11bfdcd1543fc5ab223e415808bf569b503fa6add93d69df11bfe4565e3f62c0d73d94fbf7beec05513fec05d13eec05d1be233c4f3fb8fa9f3e8d7dfebed7b75b3f82e09e3e8947d1be233c4f3fb8fa9f3e8d7dfebe9ee6493f8c65583e3acd13bf04ec633fe3db553eb429cfbed7b75b3f82e09e3e8947d1be233c4f3fb8fa9f3e8d7dfebe04ec633fe3db553eb429cfbe000000000000803f00000000c2c5473ebf147b3f0000000000000000bf147b3fc2c5473ec2c5473ebf147b3f0000000015efc33e5e836c3f00000000ea864b3ec8ad753fea864b3eea864b3ec8ad753fea864b3e00000000bf147b3fc2c5473ec2c5473ebf147b3f00000000000000005e836c3f15efc33e00000000bf147b3fc2c5473eea864b3ec8ad753fea864b3e15efc33e5e836c3f00000000da390e3f31db543f00000000b429cf3e04ec633fe3db553eda390e3f31db543f00000000f304353ff304353f0000000039cd133f9de6493f8b65583e39cd133f9de6493f8b65583eb429cf3e04ec633fe3db553eda390e3f31db543f00000000ec05d13eec05513fec05d13eb429cf3e04ec633fe3db553e39cd133f9de6493f8b65583eec05d13eec05513fec05d13ee3db553e04ec633fb429cf3eb429cf3e04ec633fe3db553ee3db553e04ec633fb429cf3e000000005e836c3f15efc33eea864b3ec8ad753fea864b3eea864b3ec8ad753fea864b3eb429cf3e04ec633fe3db553ee3db553e04ec633fb429cf3e15efc33e5e836c3f00000000b429cf3e04ec633fe3db553eea864b3ec8ad753fea864b3e00000000f304353ff304353f0000000031db543fda390e3f8c65583e9ee6493f3acd133f0000000031db543fda390e3f000000005e836c3f15efc33ee3db553e04ec633fb429cf3ee3db553e04ec633fb429cf3e8c65583e9ee6493f3acd133f0000000031db543fda390e3fec05d13eec05513fec05d13e8c65583e9ee6493f3acd133fe3db553e04ec633fb429cf3ef304353ff304353f0000000031db543fda390e3f000000009de6493f39cd133f8b65583e31db543fda390e3f000000005e836c3f15efc33e0000000004ec633fb429cf3ee3db553e04ec633fb429cf3ee3db553e9de6493f39cd133f8b65583e31db543fda390e3f00000000ec05513fec05d13eec05d13e9de6493f39cd133f8b65583e04ec633fb429cf3ee3db553e5e836c3f15efc33e000000000bfa743f32a0943e000000006c60733fba41963ec873cd3d6c60733fba41963ec873cd3d0bfa743f32a0943e00000000bf147b3fc2c5473e000000006c60733fba41963ec873cd3dbf147b3fc2c5473e00000000c8ad753fea864b3eea864b3ebf147b3fc2c5473e00000000ac3a7d3f8340163e00000000e9e57c3f0bac163e4b2f4a3de9e57c3f0bac163e4b2f4a3dac3a7d3f8340163e000000006dc47e3f35bdc83d00000000e9e57c3f0bac163e4b2f4a3d6dc47e3f35bdc83d0000000037817d3f6fb3c93d6fb3c93d0000803f00000000000000001c607f3fb8314a3db8314a3d10b17f3f30fb483d000000001c607f3fb8314a3db8314a3d37817d3f6fb3c93d6fb3c93d6dc47e3f35bdc83d0000000010b17f3f30fb483d000000001c607f3fb8314a3db8314a3d6dc47e3f35bdc83d0000000037817d3f6fb3c93d6fb3c93d1c607f3fb8314a3db8314a3de9e57c3f4b2f4a3d0bac163e1c607f3fb8314a3db8314a3d0000803f0000000000000000bf147b3f00000000c2c5473ee9e57c3f4b2f4a3d0bac163e1c607f3fb8314a3db8314a3dbf147b3f00000000c2c5473ebf147b3f00000000c2c5473e69ab7a3fd6ce4c3d9278493ee9e57c3f4b2f4a3d0bac163ee9e57c3f4b2f4a3d0bac163e69ab7a3fd6ce4c3d9278493e4ba1793f278dcc3d28aa4a3ee9e57c3f4b2f4a3d0bac163e4ba1793f278dcc3d28aa4a3e37817d3f6fb3c93d6fb3c93dc8ad753fea864b3eea864b3e3b407a3f3397183e3397183e10f7773fd817193ec0594b3e3b407a3f3397183e3397183e37817d3f6fb3c93d6fb3c93d4ba1793f278dcc3d28aa4a3e10f7773fd817193ec0594b3e3b407a3f3397183e3397183e4ba1793f278dcc3d28aa4a3e37817d3f6fb3c93d6fb3c93d3b407a3f3397183e3397183ee9e57c3f0bac163e4b2f4a3d3b407a3f3397183e3397183ec8ad753fea864b3eea864b3ebf147b3fc2c5473e00000000e9e57c3f0bac163e4b2f4a3d3b407a3f3397183e3397183ebf147b3fc2c5473e00000000c8ad753fea864b3eea864b3e10f7773fd817193ec0594b3e451a753f907c193edc9c7c3e451a753f907c193edc9c7c3e10f7773fd817193ec0594b3e4ba1793f278dcc3d28aa4a3e451a753f907c193edc9c7c3e4ba1793f278dcc3d28aa4a3e6c60733fc873cd3dba41963ebf147b3f00000000c2c5473e92de773f0efc4d3d86ca7a3e69ab7a3fd6ce4c3d9278493e92de773f0efc4d3d86ca7a3e6c60733fc873cd3dba41963e4ba1793f278dcc3d28aa4a3e69ab7a3fd6ce4c3d9278493e92de773f0efc4d3d86ca7a3e4ba1793f278dcc3d28aa4a3e6c60733fc873cd3dba41963e92de773f0efc4d3d86ca7a3e4e82703f6fef4d3d9580ad3e92de773f0efc4d3d86ca7a3ebf147b3f00000000c2c5473e5e836c3f0000000015efc33e4e82703f6fef4d3d9580ad3e92de773f0efc4d3d86ca7a3e5e836c3f0000000015efc33e5e836c3f0000000015efc33e745c6b3f3a62573d4a99c73e4e82703f6fef4d3d9580ad3e4e82703f6fef4d3d9580ad3e745c6b3f3a62573d4a99c73ea18a693f0714d73d90b2ca3e4e82703f6fef4d3d9580ad3ea18a693f0714d73d90b2ca3e6c60733fc873cd3dba41963e04ec633fe3db553eb429cf3ec4786c3f52d81e3e6355b33e370f673f61ed203ea538cd3ec4786c3f52d81e3e6355b33e6c60733fc873cd3dba41963ea18a693f0714d73d90b2ca3e370f673f61ed203ea538cd3ec4786c3f52d81e3e6355b33ea18a693f0714d73d90b2ca3e6c60733fc873cd3dba41963ec4786c3f52d81e3e6355b33e451a753f907c193edc9c7c3ec4786c3f52d81e3e6355b33e04ec633fe3db553eb429cf3ec8ad753fea864b3eea864b3e451a753f907c193edc9c7c3ec4786c3f52d81e3e6355b33ec8ad753fea864b3eea864b3e04ec633fe3db553eb429cf3ed7b75b3f82e09e3e8947d13e3b23663fa8929e3ea8929e3e3b23663fa8929e3ea8929e3ed7b75b3f82e09e3e8947d13eec05513fec05d13eec05d13e3b23663fa8929e3ea8929e3eec05513fec05d13eec05d13e04ec633fb429cf3ee3db553e04ec633fb429cf3ee3db553e76366e3f6c629b3e0bf0513e3b23663fa8929e3ea8929e3e3b23663fa8929e3ea8929e3e76366e3f6c629b3e0bf0513ec8ad753fea864b3eea864b3e3b23663fa8929e3ea8929e3ec8ad753fea864b3eea864b3e04ec633fe3db553eb429cf3ec8ad753fea864b3eea864b3e76366e3f6c629b3e0bf0513e6c60733fba41963ec873cd3d6c60733fba41963ec873cd3d76366e3f6c629b3e0bf0513e04ec633fb429cf3ee3db553e6c60733fba41963ec873cd3d04ec633fb429cf3ee3db553e5e836c3f15efc33e000000009ee6493f8c65583e3acd133f7d8e4d3f9adb223ec60d133f28ee453f111d233ee2261d3f28ee453f111d233ee2261d3f7d8e4d3f9adb223ec60d133f569b503fa6add93d69df113f28ee453f111d233ee2261d3f569b503fa6add93d69df113fb91c413f9243da3d46d4253f31db543f00000000da390e3f67a44b3fb2f85a3de2861a3fd80a533fdaff593d0843103f67a44b3fb2f85a3de2861a3fb91c413f9243da3d46d4253f569b503fa6add93d69df113fd80a533fdaff593d0843103f67a44b3fb2f85a3de2861a3f569b503fa6add93d69df113fb91c413f9243da3d46d4253f67a44b3fb2f85a3de2861a3f97773b3f76bb5a3ddecb2d3f67a44b3fb2f85a3de2861a3f31db543f00000000da390e3ff304353f00000000f304353f97773b3f76bb5a3ddecb2d3f67a44b3fb2f85a3de2861a3ff304353f00000000f304353fec05513fec05d13eec05d13ed7b75b3f82e09e3e8947d13e233c4f3fb8fa9f3e8d7dfe3e233c4f3fb8fa9f3e8d7dfe3ed7b75b3f82e09e3e8947d13e04ec633fe3db553eb429cf3e233c4f3fb8fa9f3e8d7dfe3e04ec633fe3db553eb429cf3e9ee6493f8c65583e3acd133f31db543f00000000da390e3fd80a533fdaff593d0843103f9b135a3fe839583dcc66053f9b135a3fe839583dcc66053fd80a533fdaff593d0843103f569b503fa6add93d69df113f9b135a3fe839583dcc66053f569b503fa6add93d69df113fe4565e3f62c0d73d94fbf73e9ee6493f8c65583e3acd133fdcd1543fc5ab223e4158083f7d8e4d3f9adb223ec60d133fdcd1543fc5ab223e4158083fe4565e3f62c0d73d94fbf73e569b503fa6add93d69df113f7d8e4d3f9adb223ec60d133fdcd1543fc5ab223e4158083f569b503fa6add93d69df113fe4565e3f62c0d73d94fbf73edcd1543fc5ab223e4158083f42a0613fa638213ed212e43edcd1543fc5ab223e4158083f9ee6493f8c65583e3acd133f04ec633fe3db553eb429cf3e42a0613fa638213ed212e43edcd1543fc5ab223e4158083f04ec633fe3db553eb429cf3e04ec633fe3db553eb429cf3e370f673f61ed203ea538cd3e42a0613fa638213ed212e43e42a0613fa638213ed212e43e370f673f61ed203ea538cd3ea18a693f0714d73d90b2ca3e42a0613fa638213ed212e43ea18a693f0714d73d90b2ca3ee4565e3f62c0d73d94fbf73e5e836c3f0000000015efc33ed121663f4d6a583d22a4de3e745c6b3f3a62573d4a99c73ed121663f4d6a583d22a4de3ee4565e3f62c0d73d94fbf73ea18a693f0714d73d90b2ca3e745c6b3f3a62573d4a99c73ed121663f4d6a583d22a4de3ea18a693f0714d73d90b2ca3ee4565e3f62c0d73d94fbf73ed121663f4d6a583d22a4de3e9b135a3fe839583dcc66053fd121663f4d6a583d22a4de3e5e836c3f0000000015efc33e31db543f00000000da390e3f9b135a3fe839583dcc66053fd121663f4d6a583d22a4de3e31db543f00000000da390e3ff304353f00000000f304353fdecb2d3f76bb5a3d97773b3f97773b3f76bb5a3ddecb2d3f97773b3f76bb5a3ddecb2d3fdecb2d3f76bb5a3d97773b3f46d4253f9243da3db91c413f97773b3f76bb5a3ddecb2d3f46d4253f9243da3db91c413fb91c413f9243da3d46d4253f3acd133f8c65583e9ee6493f00cc2b3f350b243e744f393fe2261d3f111d233e28ee453f00cc2b3f350b243e744f393fb91c413f9243da3d46d4253f46d4253f9243da3db91c413fe2261d3f111d233e28ee453f00cc2b3f350b243e744f393f46d4253f9243da3db91c413fb91c413f9243da3d46d4253f00cc2b3f350b243e744f393f28ee453f111d233ee2261d3f00cc2b3f350b243e744f393f3acd133f8c65583e9ee6493f9ee6493f8c65583e3acd133f28ee453f111d233ee2261d3f00cc2b3f350b243e744f393f9ee6493f8c65583e3acd133f3acd133f8c65583e9ee6493f8d7dfe3eb8fa9f3e233c4f3fbb321d3f3fc1a43eeb7e383fbb321d3f3fc1a43eeb7e383f8d7dfe3eb8fa9f3e233c4f3fec05d13eec05d13eec05513fbb321d3f3fc1a43eeb7e383fec05d13eec05d13eec05513ff7bc233f4a51da3ef7bc233ff7bc233f4a51da3ef7bc233feb7e383f3fc1a43ebb321d3fbb321d3f3fc1a43eeb7e383fbb321d3f3fc1a43eeb7e383feb7e383f3fc1a43ebb321d3f9ee6493f8c65583e3acd133fbb321d3f3fc1a43eeb7e383f9ee6493f8c65583e3acd133f3acd133f8c65583e9ee6493f9ee6493f8c65583e3acd133feb7e383f3fc1a43ebb321d3f233c4f3fb8fa9f3e8d7dfe3eeb7e383f3fc1a43ebb321d3ff7bc233f4a51da3ef7bc233fec05513fec05d13eec05d13e233c4f3fb8fa9f3e8d7dfe3eeb7e383f3fc1a43ebb321d3fec05513fec05d13eec05d13eec05d13eec05d13eec05513f8c65583e3acd133f9ee6493f4a51da3ef7bc233ff7bc233f8c65583e3acd133f9ee6493f00000000f304353ff304353f8c65583e9ee6493f3acd133f8c65583e9ee6493f3acd133f4a51da3ef7bc233ff7bc233f8c65583e3acd133f9ee6493fec05d13eec05513fec05d13e4a51da3ef7bc233ff7bc233f8c65583e9ee6493f3acd133fec05d13eec05513fec05d13ef7bc233ff7bc233f4a51da3e4a51da3ef7bc233ff7bc233ff7bc233ff7bc233f4a51da3eec05513fec05d13eec05d13ef7bc233f4a51da3ef7bc233ff7bc233f4a51da3ef7bc233f4a51da3ef7bc233ff7bc233ff7bc233ff7bc233f4a51da3eec05d13eec05d13eec05513f4a51da3ef7bc233ff7bc233ff7bc233f4a51da3ef7bc233ff304353ff304353f000000009de6493f39cd133f8b65583e39cd133f9de6493f8b65583e9de6493f39cd133f8b65583eec05513fec05d13eec05d13ef7bc233ff7bc233f4a51da3ef7bc233ff7bc233f4a51da3e39cd133f9de6493f8b65583e9de6493f39cd133f8b65583eec05d13eec05513fec05d13e39cd133f9de6493f8b65583ef7bc233ff7bc233f4a51da3e00000000c2c5473ebf147b3f4b2f4a3d0bac163ee9e57c3f000000008340163eac3a7d3f4b2f4a3d0bac163ee9e57c3f6fb3c93d6fb3c93d37817d3f0000000035bdc83d6dc47e3f000000008340163eac3a7d3f4b2f4a3d0bac163ee9e57c3f0000000035bdc83d6dc47e3f6fb3c93d6fb3c93d37817d3f0bac163e4b2f4a3de9e57c3fb7314a3db7314a3d1b607f3fb7314a3db7314a3d1b607f3f0bac163e4b2f4a3de9e57c3fc2c5473e00000000bf147b3fb7314a3db7314a3d1b607f3fc2c5473e00000000bf147b3f00000000000000000000803f00000000000000000000803f0000000030fb483d10b17f3fb7314a3db7314a3d1b607f3fb7314a3db7314a3d1b607f3f0000000030fb483d10b17f3f0000000035bdc83d6dc47e3fb7314a3db7314a3d1b607f3f0000000035bdc83d6dc47e3f6fb3c93d6fb3c93d37817d3f0000000015efc33e5e836c3fc873cd3db941963e6c60733f0000000032a0943e0bfa743fc873cd3db941963e6c60733feb864b3eeb864b3ec9ad753f00000000c2c5473ebf147b3f0000000032a0943e0bfa743fc873cd3db941963e6c60733f00000000c2c5473ebf147b3fc2c5473e00000000bf147b3f0bac163e4b2f4a3de9e57c3f9278493ed7ce4c3d69ab7a3f0bac163e4b2f4a3de9e57c3f6fb3c93d6fb3c93d37817d3f28aa4a3e288dcc3d4ba1793f9278493ed7ce4c3d69ab7a3f0bac163e4b2f4a3de9e57c3f28aa4a3e288dcc3d4ba1793f6fb3c93d6fb3c93d37817d3f4b2f4a3d0bac163ee9e57c3f3397183e3397183e3b407a3f3397183e3397183e3b407a3f4b2f4a3d0bac163ee9e57c3f00000000c2c5473ebf147b3f3397183e3397183e3b407a3f00000000c2c5473ebf147b3feb864b3eeb864b3ec9ad753feb864b3eeb864b3ec9ad753fc1594b3ed917193e10f7773f3397183e3397183e3b407a3f3397183e3397183e3b407a3fc1594b3ed917193e10f7773f28aa4a3e288dcc3d4ba1793f3397183e3397183e3b407a3f28aa4a3e288dcc3d4ba1793f6fb3c93d6fb3c93d37817d3feb864b3eeb864b3ec9ad753fdb9c7c3e917c193e451a753fc1594b3ed917193e10f7773fdb9c7c3e917c193e451a753fb941963ec873cd3d6c60733f28aa4a3e288dcc3d4ba1793fc1594b3ed917193e10f7773fdb9c7c3e917c193e451a753f28aa4a3e288dcc3d4ba1793fb941963ec873cd3d6c60733f9480ad3e6fef4d3d4e82703f85ca7a3e0efc4d3d92de773f85ca7a3e0efc4d3d92de773f9480ad3e6fef4d3d4e82703f15efc33e000000005e836c3f85ca7a3e0efc4d3d92de773f15efc33e000000005e836c3fc2c5473e00000000bf147b3fc2c5473e00000000bf147b3f9278493ed7ce4c3d69ab7a3f85ca7a3e0efc4d3d92de773f85ca7a3e0efc4d3d92de773f9278493ed7ce4c3d69ab7a3f28aa4a3e288dcc3d4ba1793f85ca7a3e0efc4d3d92de773f28aa4a3e288dcc3d4ba1793fb941963ec873cd3d6c60733f0000000015efc33e5e836c3f00000000da390e3f31db543fe3db553eb429cf3e04ec633f00000000da390e3f31db543f00000000f304353ff304353f8c65583e3acd133f9ee6493f8c65583e3acd133f9ee6493fe3db553eb429cf3e04ec633f00000000da390e3f31db543fec05d13eec05d13eec05513fe3db553eb429cf3e04ec633f8c65583e3acd133f9ee6493fb429cf3ee3db553e04ec633fa8929e3ea8929e3e3b23663f8947d13e82e09e3ed7b75b3fa8929e3ea8929e3e3b23663fe3db553eb429cf3e04ec633fec05d13eec05d13eec05513f8947d13e82e09e3ed7b75b3fa8929e3ea8929e3e3b23663fec05d13eec05d13eec05513f15efc33e000000005e836c3f9480ad3e6fef4d3d4e82703f4a99c73e3a62573d745c6b3f9480ad3e6fef4d3d4e82703fb941963ec873cd3d6c60733f90b2ca3e0714d73da18a693f4a99c73e3a62573d745c6b3f9480ad3e6fef4d3d4e82703f90b2ca3e0714d73da18a693fb941963ec873cd3d6c60733fdb9c7c3e917c193e451a753f6255b33e52d81e3ec4786c3f6255b33e52d81e3ec4786c3fdb9c7c3e917c193e451a753feb864b3eeb864b3ec9ad753f6255b33e52d81e3ec4786c3feb864b3eeb864b3ec9ad753fb429cf3ee3db553e04ec633fb429cf3ee3db553e04ec633fa538cd3e61ed203e370f673f6255b33e52d81e3ec4786c3f6255b33e52d81e3ec4786c3fa538cd3e61ed203e370f673f90b2ca3e0714d73da18a693f6255b33e52d81e3ec4786c3f90b2ca3e0714d73da18a693fb941963ec873cd3d6c60733fe3db553eb429cf3e04ec633fa8929e3ea8929e3e3b23663f0cf0513e6d629b3e76366e3fa8929e3ea8929e3e3b23663fb429cf3ee3db553e04ec633feb864b3eeb864b3ec9ad753f0cf0513e6d629b3e76366e3fa8929e3ea8929e3e3b23663feb864b3eeb864b3ec9ad753feb864b3eeb864b3ec9ad753fc873cd3db941963e6c60733f0cf0513e6d629b3e76366e3fc873cd3db941963e6c60733f0000000015efc33e5e836c3fe3db553eb429cf3e04ec633f0cf0513e6d629b3e76366e3fc873cd3db941963e6c60733fe3db553eb429cf3e04ec633f3acd133f8c65583e9ee6493fe2261d3f111d233e28ee453fc50d133f99db223e7d8e4d3fe2261d3f111d233e28ee453f46d4253f9243da3db91c413f69df113fa6add93d569b503fc50d133f99db223e7d8e4d3fe2261d3f111d233e28ee453f69df113fa6add93d569b503f46d4253f9243da3db91c413fdecb2d3f76bb5a3d97773b3fe2861a3fb2f85a3d67a44b3fe2861a3fb2f85a3d67a44b3fdecb2d3f76bb5a3d97773b3ff304353f00000000f304353fe2861a3fb2f85a3d67a44b3ff304353f00000000f304353fda390e3f0000000031db543fda390e3f0000000031db543f0843103fdaff593dd80a533fe2861a3fb2f85a3d67a44b3fe2861a3fb2f85a3d67a44b3f0843103fdaff593dd80a533f69df113fa6add93d569b503fe2861a3fb2f85a3d67a44b3f69df113fa6add93d569b503f46d4253f9243da3db91c413fb429cf3ee3db553e04ec633fd212e43ea638213e42a0613fa538cd3e61ed203e370f673fd212e43ea638213e42a0613f94fbf73e62c0d73de4565e3f90b2ca3e0714d73da18a693fa538cd3e61ed203e370f673fd212e43ea638213e42a0613f90b2ca3e0714d73da18a693f94fbf73e62c0d73de4565e3fcc66053fe839583d9b135a3f22a4de3e4d6a583dd121663f22a4de3e4d6a583dd121663fcc66053fe839583d9b135a3fda390e3f0000000031db543f22a4de3e4d6a583dd121663fda390e3f0000000031db543f15efc33e000000005e836c3f15efc33e000000005e836c3f4a99c73e3a62573d745c6b3f22a4de3e4d6a583dd121663f22a4de3e4d6a583dd121663f4a99c73e3a62573d745c6b3f90b2ca3e0714d73da18a693f22a4de3e4d6a583dd121663f90b2ca3e0714d73da18a693f94fbf73e62c0d73de4565e3fda390e3f0000000031db543fcc66053fe839583d9b135a3f0843103fdaff593dd80a533fcc66053fe839583d9b135a3f94fbf73e62c0d73de4565e3f69df113fa6add93d569b503f0843103fdaff593dd80a533fcc66053fe839583d9b135a3f69df113fa6add93d569b503f94fbf73e62c0d73de4565e3fd212e43ea638213e42a0613f4158083fc5ab223edcd1543f4158083fc5ab223edcd1543fd212e43ea638213e42a0613fb429cf3ee3db553e04ec633f4158083fc5ab223edcd1543fb429cf3ee3db553e04ec633f3acd133f8c65583e9ee6493f3acd133f8c65583e9ee6493fc50d133f99db223e7d8e4d3f4158083fc5ab223edcd1543f4158083fc5ab223edcd1543fc50d133f99db223e7d8e4d3f69df113fa6add93d569b503f4158083fc5ab223edcd1543f69df113fa6add93d569b503f94fbf73e62c0d73de4565e3fec05d13eec05d13eec05513f8d7dfe3eb8fa9f3e233c4f3f8947d13e82e09e3ed7b75b3f8d7dfe3eb8fa9f3e233c4f3f3acd133f8c65583e9ee6493fb429cf3ee3db553e04ec633f8947d13e82e09e3ed7b75b3f8d7dfe3eb8fa9f3e233c4f3fb429cf3ee3db553e04ec633f000000000000803f0000000000000000bf147b3fc2c5473ec2c547bebf147b3f0000000000000000bf147b3fc2c5473e000000005e836c3f15efc33eea864bbec8ad753fea864b3eea864bbec8ad753fea864b3ec2c547bebf147b3f0000000000000000bf147b3fc2c5473e15efc3be5e836c3f00000000c2c547bebf147b3f00000000ea864bbec8ad753fea864b3e000000005e836c3f15efc33e0000000031db543fda390e3fe3db55be04ec633fb429cf3e0000000031db543fda390e3f00000000f304353ff304353f8c6558be9ee6493f3acd133f8c6558be9ee6493f3acd133fe3db55be04ec633fb429cf3e0000000031db543fda390e3fec05d1beec05513fec05d13ee3db55be04ec633fb429cf3e8c6558be9ee6493f3acd133fec05d1beec05513fec05d13eb429cfbe04ec633fe3db553ee3db55be04ec633fb429cf3eb429cfbe04ec633fe3db553e15efc3be5e836c3f00000000ea864bbec8ad753fea864b3eea864bbec8ad753fea864b3ee3db55be04ec633fb429cf3eb429cfbe04ec633fe3db553e000000005e836c3f15efc33ee3db55be04ec633fb429cf3eea864bbec8ad753fea864b3ef30435bff304353f00000000da390ebf31db543f0000000039cd13bf9de6493f8b65583eda390ebf31db543f0000000015efc3be5e836c3f00000000b429cfbe04ec633fe3db553eb429cfbe04ec633fe3db553e39cd13bf9de6493f8b65583eda390ebf31db543f00000000ec05d1beec05513fec05d13e39cd13bf9de6493f8b65583eb429cfbe04ec633fe3db553e00000000f304353ff304353f00000000da390e3f31db543f8c6558be3acd133f9ee6493f00000000da390e3f31db543f0000000015efc33e5e836c3fe3db55beb429cf3e04ec633fe3db55beb429cf3e04ec633f8c6558be3acd133f9ee6493f00000000da390e3f31db543fec05d1beec05d13eec05513f8c6558be3acd133f9ee6493fe3db55beb429cf3e04ec633f0000000015efc33e5e836c3f0000000032a0943e0bfa743fc873cdbdb941963e6c60733fc873cdbdb941963e6c60733f0000000032a0943e0bfa743f00000000c2c5473ebf147b3fc873cdbdb941963e6c60733f00000000c2c5473ebf147b3feb864bbeeb864b3ec9ad753f00000000c2c5473ebf147b3f000000008340163eac3a7d3f4b2f4abd0bac163ee9e57c3f4b2f4abd0bac163ee9e57c3f000000008340163eac3a7d3f0000000035bdc83d6dc47e3f4b2f4abd0bac163ee9e57c3f0000000035bdc83d6dc47e3f6fb3c9bd6fb3c93d37817d3f00000000000000000000803fb7314abdb7314a3d1b607f3f0000000030fb483d10b17f3fb7314abdb7314a3d1b607f3f6fb3c9bd6fb3c93d37817d3f0000000035bdc83d6dc47e3f0000000030fb483d10b17f3fb7314abdb7314a3d1b607f3f0000000035bdc83d6dc47e3f6fb3c9bd6fb3c93d37817d3fb7314abdb7314a3d1b607f3f0bac16be4b2f4a3de9e57c3fb7314abdb7314a3d1b607f3f00000000000000000000803fc2c547be00000000bf147b3f0bac16be4b2f4a3de9e57c3fb7314abdb7314a3d1b607f3fc2c547be00000000bf147b3fc2c547be00000000bf147b3f927849bed7ce4c3d69ab7a3f0bac16be4b2f4a3de9e57c3f0bac16be4b2f4a3de9e57c3f927849bed7ce4c3d69ab7a3f28aa4abe288dcc3d4ba1793f0bac16be4b2f4a3de9e57c3f28aa4abe288dcc3d4ba1793f6fb3c9bd6fb3c93d37817d3feb864bbeeb864b3ec9ad753f339718be3397183e3b407a3fc1594bbed917193e10f7773f339718be3397183e3b407a3f6fb3c9bd6fb3c93d37817d3f28aa4abe288dcc3d4ba1793fc1594bbed917193e10f7773f339718be3397183e3b407a3f28aa4abe288dcc3d4ba1793f6fb3c9bd6fb3c93d37817d3f339718be3397183e3b407a3f4b2f4abd0bac163ee9e57c3f339718be3397183e3b407a3feb864bbeeb864b3ec9ad753f00000000c2c5473ebf147b3f4b2f4abd0bac163ee9e57c3f339718be3397183e3b407a3f00000000c2c5473ebf147b3feb864bbeeb864b3ec9ad753fc1594bbed917193e10f7773fdb9c7cbe917c193e451a753fdb9c7cbe917c193e451a753fc1594bbed917193e10f7773f28aa4abe288dcc3d4ba1793fdb9c7cbe917c193e451a753f28aa4abe288dcc3d4ba1793fb94196bec873cd3d6c60733fc2c547be00000000bf147b3f85ca7abe0efc4d3d92de773f927849bed7ce4c3d69ab7a3f85ca7abe0efc4d3d92de773fb94196bec873cd3d6c60733f28aa4abe288dcc3d4ba1793f927849bed7ce4c3d69ab7a3f85ca7abe0efc4d3d92de773f28aa4abe288dcc3d4ba1793fb94196bec873cd3d6c60733f85ca7abe0efc4d3d92de773f9480adbe6fef4d3d4e82703f85ca7abe0efc4d3d92de773fc2c547be00000000bf147b3f15efc3be000000005e836c3f9480adbe6fef4d3d4e82703f85ca7abe0efc4d3d92de773f15efc3be000000005e836c3f15efc3be000000005e836c3f4a99c7be3a62573d745c6b3f9480adbe6fef4d3d4e82703f9480adbe6fef4d3d4e82703f4a99c7be3a62573d745c6b3f90b2cabe0714d73da18a693f9480adbe6fef4d3d4e82703f90b2cabe0714d73da18a693fb94196bec873cd3d6c60733fb429cfbee3db553e04ec633f6255b3be52d81e3ec4786c3fa538cdbe61ed203e370f673f6255b3be52d81e3ec4786c3fb94196bec873cd3d6c60733f90b2cabe0714d73da18a693fa538cdbe61ed203e370f673f6255b3be52d81e3ec4786c3f90b2cabe0714d73da18a693fb94196bec873cd3d6c60733f6255b3be52d81e3ec4786c3fdb9c7cbe917c193e451a753f6255b3be52d81e3ec4786c3fb429cfbee3db553e04ec633feb864bbeeb864b3ec9ad753fdb9c7cbe917c193e451a753f6255b3be52d81e3ec4786c3feb864bbeeb864b3ec9ad753fb429cfbee3db553e04ec633f8947d1be82e09e3ed7b75b3fa8929ebea8929e3e3b23663fa8929ebea8929e3e3b23663f8947d1be82e09e3ed7b75b3fec05d1beec05d13eec05513fa8929ebea8929e3e3b23663fec05d1beec05d13eec05513fe3db55beb429cf3e04ec633fe3db55beb429cf3e04ec633f0cf051be6d629b3e76366e3fa8929ebea8929e3e3b23663fa8929ebea8929e3e3b23663f0cf051be6d629b3e76366e3feb864bbeeb864b3ec9ad753fa8929ebea8929e3e3b23663feb864bbeeb864b3ec9ad753fb429cfbee3db553e04ec633feb864bbeeb864b3ec9ad753f0cf051be6d629b3e76366e3fc873cdbdb941963e6c60733fc873cdbdb941963e6c60733f0cf051be6d629b3e76366e3fe3db55beb429cf3e04ec633fc873cdbdb941963e6c60733fe3db55beb429cf3e04ec633f0000000015efc33e5e836c3f3acd13bf8c65583e9ee6493fc50d13bf99db223e7d8e4d3fe2261dbf111d233e28ee453fe2261dbf111d233e28ee453fc50d13bf99db223e7d8e4d3f69df11bfa6add93d569b503fe2261dbf111d233e28ee453f69df11bfa6add93d569b503f46d425bf9243da3db91c413fda390ebf0000000031db543fe2861abfb2f85a3d67a44b3f084310bfdaff593dd80a533fe2861abfb2f85a3d67a44b3f46d425bf9243da3db91c413f69df11bfa6add93d569b503f084310bfdaff593dd80a533fe2861abfb2f85a3d67a44b3f69df11bfa6add93d569b503f46d425bf9243da3db91c413fe2861abfb2f85a3d67a44b3fdecb2dbf76bb5a3d97773b3fe2861abfb2f85a3d67a44b3fda390ebf0000000031db543ff30435bf00000000f304353fdecb2dbf76bb5a3d97773b3fe2861abfb2f85a3d67a44b3ff30435bf00000000f304353fec05d1beec05d13eec05513f8947d1be82e09e3ed7b75b3f8d7dfebeb8fa9f3e233c4f3f8d7dfebeb8fa9f3e233c4f3f8947d1be82e09e3ed7b75b3fb429cfbee3db553e04ec633f8d7dfebeb8fa9f3e233c4f3fb429cfbee3db553e04ec633f3acd13bf8c65583e9ee6493fda390ebf0000000031db543f084310bfdaff593dd80a533fcc6605bfe839583d9b135a3fcc6605bfe839583d9b135a3f084310bfdaff593dd80a533f69df11bfa6add93d569b503fcc6605bfe839583d9b135a3f69df11bfa6add93d569b503f94fbf7be62c0d73de4565e3f3acd13bf8c65583e9ee6493f415808bfc5ab223edcd1543fc50d13bf99db223e7d8e4d3f415808bfc5ab223edcd1543f94fbf7be62c0d73de4565e3f69df11bfa6add93d569b503fc50d13bf99db223e7d8e4d3f415808bfc5ab223edcd1543f69df11bfa6add93d569b503f94fbf7be62c0d73de4565e3f415808bfc5ab223edcd1543fd212e4bea638213e42a0613f415808bfc5ab223edcd1543f3acd13bf8c65583e9ee6493fb429cfbee3db553e04ec633fd212e4bea638213e42a0613f415808bfc5ab223edcd1543fb429cfbee3db553e04ec633fb429cfbee3db553e04ec633fa538cdbe61ed203e370f673fd212e4bea638213e42a0613fd212e4bea638213e42a0613fa538cdbe61ed203e370f673f90b2cabe0714d73da18a693fd212e4bea638213e42a0613f90b2cabe0714d73da18a693f94fbf7be62c0d73de4565e3f15efc3be000000005e836c3f22a4debe4d6a583dd121663f4a99c7be3a62573d745c6b3f22a4debe4d6a583dd121663f94fbf7be62c0d73de4565e3f90b2cabe0714d73da18a693f4a99c7be3a62573d745c6b3f22a4debe4d6a583dd121663f90b2cabe0714d73da18a693f94fbf7be62c0d73de4565e3f22a4debe4d6a583dd121663fcc6605bfe839583d9b135a3f22a4debe4d6a583dd121663f15efc3be000000005e836c3fda390ebf0000000031db543fcc6605bfe839583d9b135a3f22a4debe4d6a583dd121663fda390ebf0000000031db543ff30435bf00000000f304353f97773bbf76bb5a3ddecb2d3fdecb2dbf76bb5a3d97773b3f97773bbf76bb5a3ddecb2d3fb91c41bf9243da3d46d4253f46d425bf9243da3db91c413fdecb2dbf76bb5a3d97773b3f97773bbf76bb5a3ddecb2d3f46d425bf9243da3db91c413f9ee649bf8c65583e3acd133f744f39bf350b243e00cc2b3f28ee45bf111d233ee2261d3f744f39bf350b243e00cc2b3f46d425bf9243da3db91c413fb91c41bf9243da3d46d4253f28ee45bf111d233ee2261d3f744f39bf350b243e00cc2b3fb91c41bf9243da3d46d4253f46d425bf9243da3db91c413f744f39bf350b243e00cc2b3fe2261dbf111d233e28ee453f744f39bf350b243e00cc2b3f9ee649bf8c65583e3acd133f3acd13bf8c65583e9ee6493fe2261dbf111d233e28ee453f744f39bf350b243e00cc2b3f3acd13bf8c65583e9ee6493f9ee649bf8c65583e3acd133f233c4fbfb8fa9f3e8d7dfe3eeb7e38bf3fc1a43ebb321d3feb7e38bf3fc1a43ebb321d3f233c4fbfb8fa9f3e8d7dfe3eec0551bfec05d13eec05d13eeb7e38bf3fc1a43ebb321d3fec0551bfec05d13eec05d13ef7bc23bf4a51da3ef7bc233ff7bc23bf4a51da3ef7bc233fbb321dbf3fc1a43eeb7e383feb7e38bf3fc1a43ebb321d3feb7e38bf3fc1a43ebb321d3fbb321dbf3fc1a43eeb7e383f3acd13bf8c65583e9ee6493feb7e38bf3fc1a43ebb321d3f3acd13bf8c65583e9ee6493f9ee649bf8c65583e3acd133f3acd13bf8c65583e9ee6493fbb321dbf3fc1a43eeb7e383f8d7dfebeb8fa9f3e233c4f3fbb321dbf3fc1a43eeb7e383ff7bc23bf4a51da3ef7bc233fec05d1beec05d13eec05513f8d7dfebeb8fa9f3e233c4f3fbb321dbf3fc1a43eeb7e383fec05d1beec05d13eec05513fec0551bfec05d13eec05d13e9de649bf39cd133f8b65583ef7bc23bff7bc233f4a51da3e9de649bf39cd133f8b65583ef30435bff304353f0000000039cd13bf9de6493f8b65583e39cd13bf9de6493f8b65583ef7bc23bff7bc233f4a51da3e9de649bf39cd133f8b65583eec05d1beec05513fec05d13ef7bc23bff7bc233f4a51da3e39cd13bf9de6493f8b65583eec05d1beec05513fec05d13e4a51dabef7bc233ff7bc233ff7bc23bff7bc233f4a51da3e4a51dabef7bc233ff7bc233fec05d1beec05d13eec05513ff7bc23bf4a51da3ef7bc233ff7bc23bf4a51da3ef7bc233ff7bc23bff7bc233f4a51da3e4a51dabef7bc233ff7bc233fec0551bfec05d13eec05d13ef7bc23bff7bc233f4a51da3ef7bc23bf4a51da3ef7bc233f00000000f304353ff304353f8c6558be3acd133f9ee6493f8c6558be9ee6493f3acd133f8c6558be3acd133f9ee6493fec05d1beec05d13eec05513f4a51dabef7bc233ff7bc233f4a51dabef7bc233ff7bc233f8c6558be9ee6493f3acd133f8c6558be3acd133f9ee6493fec05d1beec05513fec05d13e8c6558be9ee6493f3acd133f4a51dabef7bc233ff7bc233fbf147bbfc2c5473e00000000e9e57cbf0bac163e4b2f4a3dac3a7dbf8340163e00000000e9e57cbf0bac163e4b2f4a3d37817dbf6fb3c93d6fb3c93d6dc47ebf35bdc83d00000000ac3a7dbf8340163e00000000e9e57cbf0bac163e4b2f4a3d6dc47ebf35bdc83d0000000037817dbf6fb3c93d6fb3c93de9e57cbf4b2f4a3d0bac163e1c607fbfb8314a3db8314a3d1c607fbfb8314a3db8314a3de9e57cbf4b2f4a3d0bac163ebf147bbf00000000c2c5473e1c607fbfb8314a3db8314a3dbf147bbf00000000c2c5473e000080bf0000000000000000000080bf000000000000000010b17fbf30fb483d000000001c607fbfb8314a3db8314a3d1c607fbfb8314a3db8314a3d10b17fbf30fb483d000000006dc47ebf35bdc83d000000001c607fbfb8314a3db8314a3d6dc47ebf35bdc83d0000000037817dbf6fb3c93d6fb3c93d5e836cbf15efc33e000000006c6073bfba41963ec873cd3d0bfa74bf32a0943e000000006c6073bfba41963ec873cd3dc8ad75bfea864b3eea864b3ebf147bbfc2c5473e000000000bfa74bf32a0943e000000006c6073bfba41963ec873cd3dbf147bbfc2c5473e00000000bf147bbf00000000c2c5473ee9e57cbf4b2f4a3d0bac163e69ab7abfd6ce4c3d9278493ee9e57cbf4b2f4a3d0bac163e37817dbf6fb3c93d6fb3c93d4ba179bf278dcc3d28aa4a3e69ab7abfd6ce4c3d9278493ee9e57cbf4b2f4a3d0bac163e4ba179bf278dcc3d28aa4a3e37817dbf6fb3c93d6fb3c93de9e57cbf0bac163e4b2f4a3d3b407abf3397183e3397183e3b407abf3397183e3397183ee9e57cbf0bac163e4b2f4a3dbf147bbfc2c5473e000000003b407abf3397183e3397183ebf147bbfc2c5473e00000000c8ad75bfea864b3eea864b3ec8ad75bfea864b3eea864b3e10f777bfd817193ec0594b3e3b407abf3397183e3397183e3b407abf3397183e3397183e10f777bfd817193ec0594b3e4ba179bf278dcc3d28aa4a3e3b407abf3397183e3397183e4ba179bf278dcc3d28aa4a3e37817dbf6fb3c93d6fb3c93dc8ad75bfea864b3eea864b3e451a75bf907c193edc9c7c3e10f777bfd817193ec0594b3e451a75bf907c193edc9c7c3e6c6073bfc873cd3dba41963e4ba179bf278dcc3d28aa4a3e10f777bfd817193ec0594b3e451a75bf907c193edc9c7c3e4ba179bf278dcc3d28aa4a3e6c6073bfc873cd3dba41963e4e8270bf6fef4d3d9580ad3e92de77bf0efc4d3d86ca7a3e92de77bf0efc4d3d86ca7a3e4e8270bf6fef4d3d9580ad3e5e836cbf0000000015efc33e92de77bf0efc4d3d86ca7a3e5e836cbf0000000015efc33ebf147bbf00000000c2c5473ebf147bbf00000000c2c5473e69ab7abfd6ce4c3d9278493e92de77bf0efc4d3d86ca7a3e92de77bf0efc4d3d86ca7a3e69ab7abfd6ce4c3d9278493e4ba179bf278dcc3d28aa4a3e92de77bf0efc4d3d86ca7a3e4ba179bf278dcc3d28aa4a3e6c6073bfc873cd3dba41963e5e836cbf15efc33e0000000031db54bfda390e3f0000000004ec63bfb429cf3ee3db553e31db54bfda390e3f00000000f30435bff304353f000000009de649bf39cd133f8b65583e9de649bf39cd133f8b65583e04ec63bfb429cf3ee3db553e31db54bfda390e3f00000000ec0551bfec05d13eec05d13e04ec63bfb429cf3ee3db553e9de649bf39cd133f8b65583e04ec63bfe3db553eb429cf3e3b2366bfa8929e3ea8929e3ed7b75bbf82e09e3e8947d13e3b2366bfa8929e3ea8929e3e04ec63bfb429cf3ee3db553eec0551bfec05d13eec05d13ed7b75bbf82e09e3e8947d13e3b2366bfa8929e3ea8929e3eec0551bfec05d13eec05d13e5e836cbf0000000015efc33e4e8270bf6fef4d3d9580ad3e745c6bbf3a62573d4a99c73e4e8270bf6fef4d3d9580ad3e6c6073bfc873cd3dba41963ea18a69bf0714d73d90b2ca3e745c6bbf3a62573d4a99c73e4e8270bf6fef4d3d9580ad3ea18a69bf0714d73d90b2ca3e6c6073bfc873cd3dba41963e451a75bf907c193edc9c7c3ec4786cbf52d81e3e6355b33ec4786cbf52d81e3e6355b33e451a75bf907c193edc9c7c3ec8ad75bfea864b3eea864b3ec4786cbf52d81e3e6355b33ec8ad75bfea864b3eea864b3e04ec63bfe3db553eb429cf3e04ec63bfe3db553eb429cf3e370f67bf61ed203ea538cd3ec4786cbf52d81e3e6355b33ec4786cbf52d81e3e6355b33e370f67bf61ed203ea538cd3ea18a69bf0714d73d90b2ca3ec4786cbf52d81e3e6355b33ea18a69bf0714d73d90b2ca3e6c6073bfc873cd3dba41963e04ec63bfb429cf3ee3db553e3b2366bfa8929e3ea8929e3e76366ebf6c629b3e0bf0513e3b2366bfa8929e3ea8929e3e04ec63bfe3db553eb429cf3ec8ad75bfea864b3eea864b3e76366ebf6c629b3e0bf0513e3b2366bfa8929e3ea8929e3ec8ad75bfea864b3eea864b3ec8ad75bfea864b3eea864b3e6c6073bfba41963ec873cd3d76366ebf6c629b3e0bf0513e6c6073bfba41963ec873cd3d5e836cbf15efc33e0000000004ec63bfb429cf3ee3db553e76366ebf6c629b3e0bf0513e6c6073bfba41963ec873cd3d04ec63bfb429cf3ee3db553e9ee649bf8c65583e3acd133f28ee45bf111d233ee2261d3f7d8e4dbf9adb223ec60d133f28ee45bf111d233ee2261d3fb91c41bf9243da3d46d4253f569b50bfa6add93d69df113f7d8e4dbf9adb223ec60d133f28ee45bf111d233ee2261d3f569b50bfa6add93d69df113fb91c41bf9243da3d46d4253f97773bbf76bb5a3ddecb2d3f67a44bbfb2f85a3de2861a3f67a44bbfb2f85a3de2861a3f97773bbf76bb5a3ddecb2d3ff30435bf00000000f304353f67a44bbfb2f85a3de2861a3ff30435bf00000000f304353f31db54bf00000000da390e3f31db54bf00000000da390e3fd80a53bfdaff593d0843103f67a44bbfb2f85a3de2861a3f67a44bbfb2f85a3de2861a3fd80a53bfdaff593d0843103f569b50bfa6add93d69df113f67a44bbfb2f85a3de2861a3f569b50bfa6add93d69df113fb91c41bf9243da3d46d4253f04ec63bfe3db553eb429cf3e42a061bfa638213ed212e43e370f67bf61ed203ea538cd3e42a061bfa638213ed212e43ee4565ebf62c0d73d94fbf73ea18a69bf0714d73d90b2ca3e370f67bf61ed203ea538cd3e42a061bfa638213ed212e43ea18a69bf0714d73d90b2ca3ee4565ebf62c0d73d94fbf73e9b135abfe839583dcc66053fd12166bf4d6a583d22a4de3ed12166bf4d6a583d22a4de3e9b135abfe839583dcc66053f31db54bf00000000da390e3fd12166bf4d6a583d22a4de3e31db54bf00000000da390e3f5e836cbf0000000015efc33e5e836cbf0000000015efc33e745c6bbf3a62573d4a99c73ed12166bf4d6a583d22a4de3ed12166bf4d6a583d22a4de3e745c6bbf3a62573d4a99c73ea18a69bf0714d73d90b2ca3ed12166bf4d6a583d22a4de3ea18a69bf0714d73d90b2ca3ee4565ebf62c0d73d94fbf73e31db54bf00000000da390e3f9b135abfe839583dcc66053fd80a53bfdaff593d0843103f9b135abfe839583dcc66053fe4565ebf62c0d73d94fbf73e569b50bfa6add93d69df113fd80a53bfdaff593d0843103f9b135abfe839583dcc66053f569b50bfa6add93d69df113fe4565ebf62c0d73d94fbf73e42a061bfa638213ed212e43edcd154bfc5ab223e4158083fdcd154bfc5ab223e4158083f42a061bfa638213ed212e43e04ec63bfe3db553eb429cf3edcd154bfc5ab223e4158083f04ec63bfe3db553eb429cf3e9ee649bf8c65583e3acd133f9ee649bf8c65583e3acd133f7d8e4dbf9adb223ec60d133fdcd154bfc5ab223e4158083fdcd154bfc5ab223e4158083f7d8e4dbf9adb223ec60d133f569b50bfa6add93d69df113fdcd154bfc5ab223e4158083f569b50bfa6add93d69df113fe4565ebf62c0d73d94fbf73eec0551bfec05d13eec05d13e233c4fbfb8fa9f3e8d7dfe3ed7b75bbf82e09e3e8947d13e233c4fbfb8fa9f3e8d7dfe3e9ee649bf8c65583e3acd133f04ec63bfe3db553eb429cf3ed7b75bbf82e09e3e8947d13e233c4fbfb8fa9f3e8d7dfe3e04ec63bfe3db553eb429cf3e000000000000803f00000000c2c547bebf147b3f0000000000000000bf147b3fc2c547bec2c547bebf147b3f0000000015efc3be5e836c3f00000000ea864bbec8ad753fea864bbeea864bbec8ad753fea864bbe00000000bf147b3fc2c547bec2c547bebf147b3f00000000000000005e836c3f15efc3be00000000bf147b3fc2c547beea864bbec8ad753fea864bbe15efc3be5e836c3f00000000da390ebf31db543f00000000b429cfbe04ec633fe3db55beda390ebf31db543f00000000f30435bff304353f0000000039cd13bf9de6493f8b6558be39cd13bf9de6493f8b6558beb429cfbe04ec633fe3db55beda390ebf31db543f00000000ec05d1beec05513fec05d1beb429cfbe04ec633fe3db55be39cd13bf9de6493f8b6558beec05d1beec05513fec05d1bee3db55be04ec633fb429cfbeb429cfbe04ec633fe3db55bee3db55be04ec633fb429cfbe000000005e836c3f15efc3beea864bbec8ad753fea864bbeea864bbec8ad753fea864bbeb429cfbe04ec633fe3db55bee3db55be04ec633fb429cfbe15efc3be5e836c3f00000000b429cfbe04ec633fe3db55beea864bbec8ad753fea864bbe00000000f304353ff30435bf0000000031db543fda390ebf8c6558be9ee6493f3acd13bf0000000031db543fda390ebf000000005e836c3f15efc3bee3db55be04ec633fb429cfbee3db55be04ec633fb429cfbe8c6558be9ee6493f3acd13bf0000000031db543fda390ebfec05d1beec05513fec05d1be8c6558be9ee6493f3acd13bfe3db55be04ec633fb429cfbef30435bff304353f0000000031db54bfda390e3f000000009de649bf39cd133f8b6558be31db54bfda390e3f000000005e836cbf15efc33e0000000004ec63bfb429cf3ee3db55be04ec63bfb429cf3ee3db55be9de649bf39cd133f8b6558be31db54bfda390e3f00000000ec0551bfec05d13eec05d1be9de649bf39cd133f8b6558be04ec63bfb429cf3ee3db55be5e836cbf15efc33e000000000bfa74bf32a0943e000000006c6073bfba41963ec873cdbd6c6073bfba41963ec873cdbd0bfa74bf32a0943e00000000bf147bbfc2c5473e000000006c6073bfba41963ec873cdbdbf147bbfc2c5473e00000000c8ad75bfea864b3eea864bbebf147bbfc2c5473e00000000ac3a7dbf8340163e00000000e9e57cbf0bac163e4b2f4abde9e57cbf0bac163e4b2f4abdac3a7dbf8340163e000000006dc47ebf35bdc83d00000000e9e57cbf0bac163e4b2f4abd6dc47ebf35bdc83d0000000037817dbf6fb3c93d6fb3c9bd000080bf00000000000000001c607fbfb8314a3db8314abd10b17fbf30fb483d000000001c607fbfb8314a3db8314abd37817dbf6fb3c93d6fb3c9bd6dc47ebf35bdc83d0000000010b17fbf30fb483d000000001c607fbfb8314a3db8314abd6dc47ebf35bdc83d0000000037817dbf6fb3c93d6fb3c9bd1c607fbfb8314a3db8314abde9e57cbf4b2f4a3d0bac16be1c607fbfb8314a3db8314abd000080bf0000000000000000bf147bbf00000000c2c547bee9e57cbf4b2f4a3d0bac16be1c607fbfb8314a3db8314abdbf147bbf00000000c2c547bebf147bbf00000000c2c547be69ab7abfd6ce4c3d927849bee9e57cbf4b2f4a3d0bac16bee9e57cbf4b2f4a3d0bac16be69ab7abfd6ce4c3d927849be4ba179bf278dcc3d28aa4abee9e57cbf4b2f4a3d0bac16be4ba179bf278dcc3d28aa4abe37817dbf6fb3c93d6fb3c9bdc8ad75bfea864b3eea864bbe3b407abf3397183e339718be10f777bfd817193ec0594bbe3b407abf3397183e339718be37817dbf6fb3c93d6fb3c9bd4ba179bf278dcc3d28aa4abe10f777bfd817193ec0594bbe3b407abf3397183e339718be4ba179bf278dcc3d28aa4abe37817dbf6fb3c93d6fb3c9bd3b407abf3397183e339718bee9e57cbf0bac163e4b2f4abd3b407abf3397183e339718bec8ad75bfea864b3eea864bbebf147bbfc2c5473e00000000e9e57cbf0bac163e4b2f4abd3b407abf3397183e339718bebf147bbfc2c5473e00000000c8ad75bfea864b3eea864bbe10f777bfd817193ec0594bbe451a75bf907c193edc9c7cbe451a75bf907c193edc9c7cbe10f777bfd817193ec0594bbe4ba179bf278dcc3d28aa4abe451a75bf907c193edc9c7cbe4ba179bf278dcc3d28aa4abe6c6073bfc873cd3dba4196bebf147bbf00000000c2c547be92de77bf0efc4d3d86ca7abe69ab7abfd6ce4c3d927849be92de77bf0efc4d3d86ca7abe6c6073bfc873cd3dba4196be4ba179bf278dcc3d28aa4abe69ab7abfd6ce4c3d927849be92de77bf0efc4d3d86ca7abe4ba179bf278dcc3d28aa4abe6c6073bfc873cd3dba4196be92de77bf0efc4d3d86ca7abe4e8270bf6fef4d3d9580adbe92de77bf0efc4d3d86ca7abebf147bbf00000000c2c547be5e836cbf0000000015efc3be4e8270bf6fef4d3d9580adbe92de77bf0efc4d3d86ca7abe5e836cbf0000000015efc3be5e836cbf0000000015efc3be745c6bbf3a62573d4a99c7be4e8270bf6fef4d3d9580adbe4e8270bf6fef4d3d9580adbe745c6bbf3a62573d4a99c7bea18a69bf0714d73d90b2cabe4e8270bf6fef4d3d9580adbea18a69bf0714d73d90b2cabe6c6073bfc873cd3dba4196be04ec63bfe3db553eb429cfbec4786cbf52d81e3e6355b3be370f67bf61ed203ea538cdbec4786cbf52d81e3e6355b3be6c6073bfc873cd3dba4196bea18a69bf0714d73d90b2cabe370f67bf61ed203ea538cdbec4786cbf52d81e3e6355b3bea18a69bf0714d73d90b2cabe6c6073bfc873cd3dba4196bec4786cbf52d81e3e6355b3be451a75bf907c193edc9c7cbec4786cbf52d81e3e6355b3be04ec63bfe3db553eb429cfbec8ad75bfea864b3eea864bbe451a75bf907c193edc9c7cbec4786cbf52d81e3e6355b3bec8ad75bfea864b3eea864bbe04ec63bfe3db553eb429cfbed7b75bbf82e09e3e8947d1be3b2366bfa8929e3ea8929ebe3b2366bfa8929e3ea8929ebed7b75bbf82e09e3e8947d1beec0551bfec05d13eec05d1be3b2366bfa8929e3ea8929ebeec0551bfec05d13eec05d1be04ec63bfb429cf3ee3db55be04ec63bfb429cf3ee3db55be76366ebf6c629b3e0bf051be3b2366bfa8929e3ea8929ebe3b2366bfa8929e3ea8929ebe76366ebf6c629b3e0bf051bec8ad75bfea864b3eea864bbe3b2366bfa8929e3ea8929ebec8ad75bfea864b3eea864bbe04ec63bfe3db553eb429cfbec8ad75bfea864b3eea864bbe76366ebf6c629b3e0bf051be6c6073bfba41963ec873cdbd6c6073bfba41963ec873cdbd76366ebf6c629b3e0bf051be04ec63bfb429cf3ee3db55be6c6073bfba41963ec873cdbd04ec63bfb429cf3ee3db55be5e836cbf15efc33e000000009ee649bf8c65583e3acd13bf7d8e4dbf9adb223ec60d13bf28ee45bf111d233ee2261dbf28ee45bf111d233ee2261dbf7d8e4dbf9adb223ec60d13bf569b50bfa6add93d69df11bf28ee45bf111d233ee2261dbf569b50bfa6add93d69df11bfb91c41bf9243da3d46d425bf31db54bf00000000da390ebf67a44bbfb2f85a3de2861abfd80a53bfdaff593d084310bf67a44bbfb2f85a3de2861abfb91c41bf9243da3d46d425bf569b50bfa6add93d69df11bfd80a53bfdaff593d084310bf67a44bbfb2f85a3de2861abf569b50bfa6add93d69df11bfb91c41bf9243da3d46d425bf67a44bbfb2f85a3de2861abf97773bbf76bb5a3ddecb2dbf67a44bbfb2f85a3de2861abf31db54bf00000000da390ebff30435bf00000000f30435bf97773bbf76bb5a3ddecb2dbf67a44bbfb2f85a3de2861abff30435bf00000000f30435bfec0551bfec05d13eec05d1bed7b75bbf82e09e3e8947d1be233c4fbfb8fa9f3e8d7dfebe233c4fbfb8fa9f3e8d7dfebed7b75bbf82e09e3e8947d1be04ec63bfe3db553eb429cfbe233c4fbfb8fa9f3e8d7dfebe04ec63bfe3db553eb429cfbe9ee649bf8c65583e3acd13bf31db54bf00000000da390ebfd80a53bfdaff593d084310bf9b135abfe839583dcc6605bf9b135abfe839583dcc6605bfd80a53bfdaff593d084310bf569b50bfa6add93d69df11bf9b135abfe839583dcc6605bf569b50bfa6add93d69df11bfe4565ebf62c0d73d94fbf7be9ee649bf8c65583e3acd13bfdcd154bfc5ab223e415808bf7d8e4dbf9adb223ec60d13bfdcd154bfc5ab223e415808bfe4565ebf62c0d73d94fbf7be569b50bfa6add93d69df11bf7d8e4dbf9adb223ec60d13bfdcd154bfc5ab223e415808bf569b50bfa6add93d69df11bfe4565ebf62c0d73d94fbf7bedcd154bfc5ab223e415808bf42a061bfa638213ed212e4bedcd154bfc5ab223e415808bf9ee649bf8c65583e3acd13bf04ec63bfe3db553eb429cfbe42a061bfa638213ed212e4bedcd154bfc5ab223e415808bf04ec63bfe3db553eb429cfbe04ec63bfe3db553eb429cfbe370f67bf61ed203ea538cdbe42a061bfa638213ed212e4be42a061bfa638213ed212e4be370f67bf61ed203ea538cdbea18a69bf0714d73d90b2cabe42a061bfa638213ed212e4bea18a69bf0714d73d90b2cabee4565ebf62c0d73d94fbf7be5e836cbf0000000015efc3bed12166bf4d6a583d22a4debe745c6bbf3a62573d4a99c7bed12166bf4d6a583d22a4debee4565ebf62c0d73d94fbf7bea18a69bf0714d73d90b2cabe745c6bbf3a62573d4a99c7bed12166bf4d6a583d22a4debea18a69bf0714d73d90b2cabee4565ebf62c0d73d94fbf7bed12166bf4d6a583d22a4debe9b135abfe839583dcc6605bfd12166bf4d6a583d22a4debe5e836cbf0000000015efc3be31db54bf00000000da390ebf9b135abfe839583dcc6605bfd12166bf4d6a583d22a4debe31db54bf00000000da390ebff30435bf00000000f30435bfdecb2dbf76bb5a3d97773bbf97773bbf76bb5a3ddecb2dbf97773bbf76bb5a3ddecb2dbfdecb2dbf76bb5a3d97773bbf46d425bf9243da3db91c41bf97773bbf76bb5a3ddecb2dbf46d425bf9243da3db91c41bfb91c41bf9243da3d46d425bf3acd13bf8c65583e9ee649bf00cc2bbf350b243e744f39bfe2261dbf111d233e28ee45bf00cc2bbf350b243e744f39bfb91c41bf9243da3d46d425bf46d425bf9243da3db91c41bfe2261dbf111d233e28ee45bf00cc2bbf350b243e744f39bf46d425bf9243da3db91c41bfb91c41bf9243da3d46d425bf00cc2bbf350b243e744f39bf28ee45bf111d233ee2261dbf00cc2bbf350b243e744f39bf3acd13bf8c65583e9ee649bf9ee649bf8c65583e3acd13bf28ee45bf111d233ee2261dbf00cc2bbf350b243e744f39bf9ee649bf8c65583e3acd13bf3acd13bf8c65583e9ee649bf8d7dfebeb8fa9f3e233c4fbfbb321dbf3fc1a43eeb7e38bfbb321dbf3fc1a43eeb7e38bf8d7dfebeb8fa9f3e233c4fbfec05d1beec05d13eec0551bfbb321dbf3fc1a43eeb7e38bfec05d1beec05d13eec0551bff7bc23bf4a51da3ef7bc23bff7bc23bf4a51da3ef7bc23bfeb7e38bf3fc1a43ebb321dbfbb321dbf3fc1a43eeb7e38bfbb321dbf3fc1a43eeb7e38bfeb7e38bf3fc1a43ebb321dbf9ee649bf8c65583e3acd13bfbb321dbf3fc1a43eeb7e38bf9ee649bf8c65583e3acd13bf3acd13bf8c65583e9ee649bf9ee649bf8c65583e3acd13bfeb7e38bf3fc1a43ebb321dbf233c4fbfb8fa9f3e8d7dfebeeb7e38bf3fc1a43ebb321dbff7bc23bf4a51da3ef7bc23bfec0551bfec05d13eec05d1be233c4fbfb8fa9f3e8d7dfebeeb7e38bf3fc1a43ebb321dbfec0551bfec05d13eec05d1beec05d1beec05d13eec0551bf8c6558be3acd133f9ee649bf4a51dabef7bc233ff7bc23bf8c6558be3acd133f9ee649bf00000000f304353ff30435bf8c6558be9ee6493f3acd13bf8c6558be9ee6493f3acd13bf4a51dabef7bc233ff7bc23bf8c6558be3acd133f9ee649bfec05d1beec05513fec05d1be4a51dabef7bc233ff7bc23bf8c6558be9ee6493f3acd13bfec05d1beec05513fec05d1bef7bc23bff7bc233f4a51dabe4a51dabef7bc233ff7bc23bff7bc23bff7bc233f4a51dabeec0551bfec05d13eec05d1bef7bc23bf4a51da3ef7bc23bff7bc23bf4a51da3ef7bc23bf4a51dabef7bc233ff7bc23bff7bc23bff7bc233f4a51dabeec05d1beec05d13eec0551bf4a51dabef7bc233ff7bc23bff7bc23bf4a51da3ef7bc23bff30435bff304353f000000009de649bf39cd133f8b6558be39cd13bf9de6493f8b6558be9de649bf39cd133f8b6558beec0551bfec05d13eec05d1bef7bc23bff7bc233f4a51dabef7bc23bff7bc233f4a51dabe39cd13bf9de6493f8b6558be9de649bf39cd133f8b6558beec05d1beec05513fec05d1be39cd13bf9de6493f8b6558bef7bc23bff7bc233f4a51dabe00000000c2c5473ebf147bbf4b2f4abd0bac163ee9e57cbf000000008340163eac3a7dbf4b2f4abd0bac163ee9e57cbf6fb3c9bd6fb3c93d37817dbf0000000035bdc83d6dc47ebf000000008340163eac3a7dbf4b2f4abd0bac163ee9e57cbf0000000035bdc83d6dc47ebf6fb3c9bd6fb3c93d37817dbf0bac16be4b2f4a3de9e57cbfb7314abdb7314a3d1b607fbfb7314abdb7314a3d1b607fbf0bac16be4b2f4a3de9e57cbfc2c547be00000000bf147bbfb7314abdb7314a3d1b607fbfc2c547be00000000bf147bbf0000000000000000000080bf0000000000000000000080bf0000000030fb483d10b17fbfb7314abdb7314a3d1b607fbfb7314abdb7314a3d1b607fbf0000000030fb483d10b17fbf0000000035bdc83d6dc47ebfb7314abdb7314a3d1b607fbf0000000035bdc83d6dc47ebf6fb3c9bd6fb3c93d37817dbf0000000015efc33e5e836cbfc873cdbdb941963e6c6073bf0000000032a0943e0bfa74bfc873cdbdb941963e6c6073bfeb864bbeeb864b3ec9ad75bf00000000c2c5473ebf147bbf0000000032a0943e0bfa74bfc873cdbdb941963e6c6073bf00000000c2c5473ebf147bbfc2c547be00000000bf147bbf0bac16be4b2f4a3de9e57cbf927849bed7ce4c3d69ab7abf0bac16be4b2f4a3de9e57cbf6fb3c9bd6fb3c93d37817dbf28aa4abe288dcc3d4ba179bf927849bed7ce4c3d69ab7abf0bac16be4b2f4a3de9e57cbf28aa4abe288dcc3d4ba179bf6fb3c9bd6fb3c93d37817dbf4b2f4abd0bac163ee9e57cbf339718be3397183e3b407abf339718be3397183e3b407abf4b2f4abd0bac163ee9e57cbf00000000c2c5473ebf147bbf339718be3397183e3b407abf00000000c2c5473ebf147bbfeb864bbeeb864b3ec9ad75bfeb864bbeeb864b3ec9ad75bfc1594bbed917193e10f777bf339718be3397183e3b407abf339718be3397183e3b407abfc1594bbed917193e10f777bf28aa4abe288dcc3d4ba179bf339718be3397183e3b407abf28aa4abe288dcc3d4ba179bf6fb3c9bd6fb3c93d37817dbfeb864bbeeb864b3ec9ad75bfdb9c7cbe917c193e451a75bfc1594bbed917193e10f777bfdb9c7cbe917c193e451a75bfb94196bec873cd3d6c6073bf28aa4abe288dcc3d4ba179bfc1594bbed917193e10f777bfdb9c7cbe917c193e451a75bf28aa4abe288dcc3d4ba179bfb94196bec873cd3d6c6073bf9480adbe6fef4d3d4e8270bf85ca7abe0efc4d3d92de77bf85ca7abe0efc4d3d92de77bf9480adbe6fef4d3d4e8270bf15efc3be000000005e836cbf85ca7abe0efc4d3d92de77bf15efc3be000000005e836cbfc2c547be00000000bf147bbfc2c547be00000000bf147bbf927849bed7ce4c3d69ab7abf85ca7abe0efc4d3d92de77bf85ca7abe0efc4d3d92de77bf927849bed7ce4c3d69ab7abf28aa4abe288dcc3d4ba179bf85ca7abe0efc4d3d92de77bf28aa4abe288dcc3d4ba179bfb94196bec873cd3d6c6073bf0000000015efc33e5e836cbf00000000da390e3f31db54bfe3db55beb429cf3e04ec63bf00000000da390e3f31db54bf00000000f304353ff30435bf8c6558be3acd133f9ee649bf8c6558be3acd133f9ee649bfe3db55beb429cf3e04ec63bf00000000da390e3f31db54bfec05d1beec05d13eec0551bfe3db55beb429cf3e04ec63bf8c6558be3acd133f9ee649bfb429cfbee3db553e04ec63bfa8929ebea8929e3e3b2366bf8947d1be82e09e3ed7b75bbfa8929ebea8929e3e3b2366bfe3db55beb429cf3e04ec63bfec05d1beec05d13eec0551bf8947d1be82e09e3ed7b75bbfa8929ebea8929e3e3b2366bfec05d1beec05d13eec0551bf15efc3be000000005e836cbf9480adbe6fef4d3d4e8270bf4a99c7be3a62573d745c6bbf9480adbe6fef4d3d4e8270bfb94196bec873cd3d6c6073bf90b2cabe0714d73da18a69bf4a99c7be3a62573d745c6bbf9480adbe6fef4d3d4e8270bf90b2cabe0714d73da18a69bfb94196bec873cd3d6c6073bfdb9c7cbe917c193e451a75bf6255b3be52d81e3ec4786cbf6255b3be52d81e3ec4786cbfdb9c7cbe917c193e451a75bfeb864bbeeb864b3ec9ad75bf6255b3be52d81e3ec4786cbfeb864bbeeb864b3ec9ad75bfb429cfbee3db553e04ec63bfb429cfbee3db553e04ec63bfa538cdbe61ed203e370f67bf6255b3be52d81e3ec4786cbf6255b3be52d81e3ec4786cbfa538cdbe61ed203e370f67bf90b2cabe0714d73da18a69bf6255b3be52d81e3ec4786cbf90b2cabe0714d73da18a69bfb94196bec873cd3d6c6073bfe3db55beb429cf3e04ec63bfa8929ebea8929e3e3b2366bf0cf051be6d629b3e76366ebfa8929ebea8929e3e3b2366bfb429cfbee3db553e04ec63bfeb864bbeeb864b3ec9ad75bf0cf051be6d629b3e76366ebfa8929ebea8929e3e3b2366bfeb864bbeeb864b3ec9ad75bfeb864bbeeb864b3ec9ad75bfc873cdbdb941963e6c6073bf0cf051be6d629b3e76366ebfc873cdbdb941963e6c6073bf0000000015efc33e5e836cbfe3db55beb429cf3e04ec63bf0cf051be6d629b3e76366ebfc873cdbdb941963e6c6073bfe3db55beb429cf3e04ec63bf3acd13bf8c65583e9ee649bfe2261dbf111d233e28ee45bfc50d13bf99db223e7d8e4dbfe2261dbf111d233e28ee45bf46d425bf9243da3db91c41bf69df11bfa6add93d569b50bfc50d13bf99db223e7d8e4dbfe2261dbf111d233e28ee45bf69df11bfa6add93d569b50bf46d425bf9243da3db91c41bfdecb2dbf76bb5a3d97773bbfe2861abfb2f85a3d67a44bbfe2861abfb2f85a3d67a44bbfdecb2dbf76bb5a3d97773bbff30435bf00000000f30435bfe2861abfb2f85a3d67a44bbff30435bf00000000f30435bfda390ebf0000000031db54bfda390ebf0000000031db54bf084310bfdaff593dd80a53bfe2861abfb2f85a3d67a44bbfe2861abfb2f85a3d67a44bbf084310bfdaff593dd80a53bf69df11bfa6add93d569b50bfe2861abfb2f85a3d67a44bbf69df11bfa6add93d569b50bf46d425bf9243da3db91c41bfb429cfbee3db553e04ec63bfd212e4bea638213e42a061bfa538cdbe61ed203e370f67bfd212e4bea638213e42a061bf94fbf7be62c0d73de4565ebf90b2cabe0714d73da18a69bfa538cdbe61ed203e370f67bfd212e4bea638213e42a061bf90b2cabe0714d73da18a69bf94fbf7be62c0d73de4565ebfcc6605bfe839583d9b135abf22a4debe4d6a583dd12166bf22a4debe4d6a583dd12166bfcc6605bfe839583d9b135abfda390ebf0000000031db54bf22a4debe4d6a583dd12166bfda390ebf0000000031db54bf15efc3be000000005e836cbf15efc3be000000005e836cbf4a99c7be3a62573d745c6bbf22a4debe4d6a583dd12166bf22a4debe4d6a583dd12166bf4a99c7be3a62573d745c6bbf90b2cabe0714d73da18a69bf22a4debe4d6a583dd12166bf90b2cabe0714d73da18a69bf94fbf7be62c0d73de4565ebfda390ebf0000000031db54bfcc6605bfe839583d9b135abf084310bfdaff593dd80a53bfcc6605bfe839583d9b135abf94fbf7be62c0d73de4565ebf69df11bfa6add93d569b50bf084310bfdaff593dd80a53bfcc6605bfe839583d9b135abf69df11bfa6add93d569b50bf94fbf7be62c0d73de4565ebfd212e4bea638213e42a061bf415808bfc5ab223edcd154bf415808bfc5ab223edcd154bfd212e4bea638213e42a061bfb429cfbee3db553e04ec63bf415808bfc5ab223edcd154bfb429cfbee3db553e04ec63bf3acd13bf8c65583e9ee649bf3acd13bf8c65583e9ee649bfc50d13bf99db223e7d8e4dbf415808bfc5ab223edcd154bf415808bfc5ab223edcd154bfc50d13bf99db223e7d8e4dbf69df11bfa6add93d569b50bf415808bfc5ab223edcd154bf69df11bfa6add93d569b50bf94fbf7be62c0d73de4565ebfec05d1beec05d13eec0551bf8d7dfebeb8fa9f3e233c4fbf8947d1be82e09e3ed7b75bbf8d7dfebeb8fa9f3e233c4fbf3acd13bf8c65583e9ee649bfb429cfbee3db553e04ec63bf8947d1be82e09e3ed7b75bbf8d7dfebeb8fa9f3e233c4fbfb429cfbee3db553e04ec63bf00000000000080bf00000000c2c5473ebf147bbf0000000000000000bf147bbfc2c547bec2c5473ebf147bbf0000000015efc33e5e836cbf00000000ea864b3ec8ad75bfea864bbeea864b3ec8ad75bfea864bbe00000000bf147bbfc2c547bec2c5473ebf147bbf00000000000000005e836cbf15efc3be00000000bf147bbfc2c547beea864b3ec8ad75bfea864bbe15efc33e5e836cbf00000000da390e3f31db54bf00000000b429cf3e04ec63bfe3db55beda390e3f31db54bf00000000f304353ff30435bf0000000039cd133f9de649bf8b6558be39cd133f9de649bf8b6558beb429cf3e04ec63bfe3db55beda390e3f31db54bf00000000ec05d13eec0551bfec05d1beb429cf3e04ec63bfe3db55be39cd133f9de649bf8b6558beec05d13eec0551bfec05d1bee3db553e04ec63bfb429cfbeb429cf3e04ec63bfe3db55bee3db553e04ec63bfb429cfbe000000005e836cbf15efc3beea864b3ec8ad75bfea864bbeea864b3ec8ad75bfea864bbeb429cf3e04ec63bfe3db55bee3db553e04ec63bfb429cfbe15efc33e5e836cbf00000000b429cf3e04ec63bfe3db55beea864b3ec8ad75bfea864bbe00000000f30435bff30435bf0000000031db54bfda390ebf8c65583e9ee649bf3acd13bf0000000031db54bfda390ebf000000005e836cbf15efc3bee3db553e04ec63bfb429cfbee3db553e04ec63bfb429cfbe8c65583e9ee649bf3acd13bf0000000031db54bfda390ebfec05d13eec0551bfec05d1be8c65583e9ee649bf3acd13bfe3db553e04ec63bfb429cfbef304353ff30435bf0000000031db543fda390ebf000000009de6493f39cd13bf8b6558be31db543fda390ebf000000005e836c3f15efc3be0000000004ec633fb429cfbee3db55be04ec633fb429cfbee3db55be9de6493f39cd13bf8b6558be31db543fda390ebf00000000ec05513fec05d1beec05d1be9de6493f39cd13bf8b6558be04ec633fb429cfbee3db55be5e836c3f15efc3be000000000bfa743f32a094be000000006c60733fba4196bec873cdbd6c60733fba4196bec873cdbd0bfa743f32a094be00000000bf147b3fc2c547be000000006c60733fba4196bec873cdbdbf147b3fc2c547be00000000c8ad753fea864bbeea864bbebf147b3fc2c547be00000000ac3a7d3f834016be00000000e9e57c3f0bac16be4b2f4abde9e57c3f0bac16be4b2f4abdac3a7d3f834016be000000006dc47e3f35bdc8bd00000000e9e57c3f0bac16be4b2f4abd6dc47e3f35bdc8bd0000000037817d3f6fb3c9bd6fb3c9bd0000803f00000000000000001c607f3fb8314abdb8314abd10b17f3f30fb48bd000000001c607f3fb8314abdb8314abd37817d3f6fb3c9bd6fb3c9bd6dc47e3f35bdc8bd0000000010b17f3f30fb48bd000000001c607f3fb8314abdb8314abd6dc47e3f35bdc8bd0000000037817d3f6fb3c9bd6fb3c9bd1c607f3fb8314abdb8314abde9e57c3f4b2f4abd0bac16be1c607f3fb8314abdb8314abd0000803f0000000000000000bf147b3f00000000c2c547bee9e57c3f4b2f4abd0bac16be1c607f3fb8314abdb8314abdbf147b3f00000000c2c547bebf147b3f00000000c2c547be69ab7a3fd6ce4cbd927849bee9e57c3f4b2f4abd0bac16bee9e57c3f4b2f4abd0bac16be69ab7a3fd6ce4cbd927849be4ba1793f278dccbd28aa4abee9e57c3f4b2f4abd0bac16be4ba1793f278dccbd28aa4abe37817d3f6fb3c9bd6fb3c9bdc8ad753fea864bbeea864bbe3b407a3f339718be339718be10f7773fd81719bec0594bbe3b407a3f339718be339718be37817d3f6fb3c9bd6fb3c9bd4ba1793f278dccbd28aa4abe10f7773fd81719bec0594bbe3b407a3f339718be339718be4ba1793f278dccbd28aa4abe37817d3f6fb3c9bd6fb3c9bd3b407a3f339718be339718bee9e57c3f0bac16be4b2f4abd3b407a3f339718be339718bec8ad753fea864bbeea864bbebf147b3fc2c547be00000000e9e57c3f0bac16be4b2f4abd3b407a3f339718be339718bebf147b3fc2c547be00000000c8ad753fea864bbeea864bbe10f7773fd81719bec0594bbe451a753f907c19bedc9c7cbe451a753f907c19bedc9c7cbe10f7773fd81719bec0594bbe4ba1793f278dccbd28aa4abe451a753f907c19bedc9c7cbe4ba1793f278dccbd28aa4abe6c60733fc873cdbdba4196bebf147b3f00000000c2c547be92de773f0efc4dbd86ca7abe69ab7a3fd6ce4cbd927849be92de773f0efc4dbd86ca7abe6c60733fc873cdbdba4196be4ba1793f278dccbd28aa4abe69ab7a3fd6ce4cbd927849be92de773f0efc4dbd86ca7abe4ba1793f278dccbd28aa4abe6c60733fc873cdbdba4196be92de773f0efc4dbd86ca7abe4e82703f6fef4dbd9580adbe92de773f0efc4dbd86ca7abebf147b3f00000000c2c547be5e836c3f0000000015efc3be4e82703f6fef4dbd9580adbe92de773f0efc4dbd86ca7abe5e836c3f0000000015efc3be5e836c3f0000000015efc3be745c6b3f3a6257bd4a99c7be4e82703f6fef4dbd9580adbe4e82703f6fef4dbd9580adbe745c6b3f3a6257bd4a99c7bea18a693f0714d7bd90b2cabe4e82703f6fef4dbd9580adbea18a693f0714d7bd90b2cabe6c60733fc873cdbdba4196be04ec633fe3db55beb429cfbec4786c3f52d81ebe6355b3be370f673f61ed20bea538cdbec4786c3f52d81ebe6355b3be6c60733fc873cdbdba4196bea18a693f0714d7bd90b2cabe370f673f61ed20bea538cdbec4786c3f52d81ebe6355b3bea18a693f0714d7bd90b2cabe6c60733fc873cdbdba4196bec4786c3f52d81ebe6355b3be451a753f907c19bedc9c7cbec4786c3f52d81ebe6355b3be04ec633fe3db55beb429cfbec8ad753fea864bbeea864bbe451a753f907c19bedc9c7cbec4786c3f52d81ebe6355b3bec8ad753fea864bbeea864bbe04ec633fe3db55beb429cfbed7b75b3f82e09ebe8947d1be3b23663fa8929ebea8929ebe3b23663fa8929ebea8929ebed7b75b3f82e09ebe8947d1beec05513fec05d1beec05d1be3b23663fa8929ebea8929ebeec05513fec05d1beec05d1be04ec633fb429cfbee3db55be04ec633fb429cfbee3db55be76366e3f6c629bbe0bf051be3b23663fa8929ebea8929ebe3b23663fa8929ebea8929ebe76366e3f6c629bbe0bf051bec8ad753fea864bbeea864bbe3b23663fa8929ebea8929ebec8ad753fea864bbeea864bbe04ec633fe3db55beb429cfbec8ad753fea864bbeea864bbe76366e3f6c629bbe0bf051be6c60733fba4196bec873cdbd6c60733fba4196bec873cdbd76366e3f6c629bbe0bf051be04ec633fb429cfbee3db55be6c60733fba4196bec873cdbd04ec633fb429cfbee3db55be5e836c3f15efc3be000000009ee6493f8c6558be3acd13bf7d8e4d3f9adb22bec60d13bf28ee453f111d23bee2261dbf28ee453f111d23bee2261dbf7d8e4d3f9adb22bec60d13bf569b503fa6add9bd69df11bf28ee453f111d23bee2261dbf569b503fa6add9bd69df11bfb91c413f9243dabd46d425bf31db543f00000000da390ebf67a44b3fb2f85abde2861abfd80a533fdaff59bd084310bf67a44b3fb2f85abde2861abfb91c413f9243dabd46d425bf569b503fa6add9bd69df11bfd80a533fdaff59bd084310bf67a44b3fb2f85abde2861abf569b503fa6add9bd69df11bfb91c413f9243dabd46d425bf67a44b3fb2f85abde2861abf97773b3f76bb5abddecb2dbf67a44b3fb2f85abde2861abf31db543f00000000da390ebff304353f00000000f30435bf97773b3f76bb5abddecb2dbf67a44b3fb2f85abde2861abff304353f00000000f30435bfec05513fec05d1beec05d1bed7b75b3f82e09ebe8947d1be233c4f3fb8fa9fbe8d7dfebe233c4f3fb8fa9fbe8d7dfebed7b75b3f82e09ebe8947d1be04ec633fe3db55beb429cfbe233c4f3fb8fa9fbe8d7dfebe04ec633fe3db55beb429cfbe9ee6493f8c6558be3acd13bf31db543f00000000da390ebfd80a533fdaff59bd084310bf9b135a3fe83958bdcc6605bf9b135a3fe83958bdcc6605bfd80a533fdaff59bd084310bf569b503fa6add9bd69df11bf9b135a3fe83958bdcc6605bf569b503fa6add9bd69df11bfe4565e3f62c0d7bd94fbf7be9ee6493f8c6558be3acd13bfdcd1543fc5ab22be415808bf7d8e4d3f9adb22bec60d13bfdcd1543fc5ab22be415808bfe4565e3f62c0d7bd94fbf7be569b503fa6add9bd69df11bf7d8e4d3f9adb22bec60d13bfdcd1543fc5ab22be415808bf569b503fa6add9bd69df11bfe4565e3f62c0d7bd94fbf7bedcd1543fc5ab22be415808bf42a0613fa63821bed212e4bedcd1543fc5ab22be415808bf9ee6493f8c6558be3acd13bf04ec633fe3db55beb429cfbe42a0613fa63821bed212e4bedcd1543fc5ab22be415808bf04ec633fe3db55beb429cfbe04ec633fe3db55beb429cfbe370f673f61ed20bea538cdbe42a0613fa63821bed212e4be42a0613fa63821bed212e4be370f673f61ed20bea538cdbea18a693f0714d7bd90b2cabe42a0613fa63821bed212e4bea18a693f0714d7bd90b2cabee4565e3f62c0d7bd94fbf7be5e836c3f0000000015efc3bed121663f4d6a58bd22a4debe745c6b3f3a6257bd4a99c7bed121663f4d6a58bd22a4debee4565e3f62c0d7bd94fbf7bea18a693f0714d7bd90b2cabe745c6b3f3a6257bd4a99c7bed121663f4d6a58bd22a4debea18a693f0714d7bd90b2cabee4565e3f62c0d7bd94fbf7bed121663f4d6a58bd22a4debe9b135a3fe83958bdcc6605bfd121663f4d6a58bd22a4debe5e836c3f0000000015efc3be31db543f00000000da390ebf9b135a3fe83958bdcc6605bfd121663f4d6a58bd22a4debe31db543f00000000da390ebff304353f00000000f30435bfdecb2d3f76bb5abd97773bbf97773b3f76bb5abddecb2dbf97773b3f76bb5abddecb2dbfdecb2d3f76bb5abd97773bbf46d4253f9243dabdb91c41bf97773b3f76bb5abddecb2dbf46d4253f9243dabdb91c41bfb91c413f9243dabd46d425bf3acd133f8c6558be9ee649bf00cc2b3f350b24be744f39bfe2261d3f111d23be28ee45bf00cc2b3f350b24be744f39bfb91c413f9243dabd46d425bf46d4253f9243dabdb91c41bfe2261d3f111d23be28ee45bf00cc2b3f350b24be744f39bf46d4253f9243dabdb91c41bfb91c413f9243dabd46d425bf00cc2b3f350b24be744f39bf28ee453f111d23bee2261dbf00cc2b3f350b24be744f39bf3acd133f8c6558be9ee649bf9ee6493f8c6558be3acd13bf28ee453f111d23bee2261dbf00cc2b3f350b24be744f39bf9ee6493f8c6558be3acd13bf3acd133f8c6558be9ee649bf8d7dfe3eb8fa9fbe233c4fbfbb321d3f3fc1a4beeb7e38bfbb321d3f3fc1a4beeb7e38bf8d7dfe3eb8fa9fbe233c4fbfec05d13eec05d1beec0551bfbb321d3f3fc1a4beeb7e38bfec05d13eec05d1beec0551bff7bc233f4a51dabef7bc23bff7bc233f4a51dabef7bc23bfeb7e383f3fc1a4bebb321dbfbb321d3f3fc1a4beeb7e38bfbb321d3f3fc1a4beeb7e38bfeb7e383f3fc1a4bebb321dbf9ee6493f8c6558be3acd13bfbb321d3f3fc1a4beeb7e38bf9ee6493f8c6558be3acd13bf3acd133f8c6558be9ee649bf9ee6493f8c6558be3acd13bfeb7e383f3fc1a4bebb321dbf233c4f3fb8fa9fbe8d7dfebeeb7e383f3fc1a4bebb321dbff7bc233f4a51dabef7bc23bfec05513fec05d1beec05d1be233c4f3fb8fa9fbe8d7dfebeeb7e383f3fc1a4bebb321dbfec05513fec05d1beec05d1beec05d13eec05d1beec0551bf8c65583e3acd13bf9ee649bf4a51da3ef7bc23bff7bc23bf8c65583e3acd13bf9ee649bf00000000f30435bff30435bf8c65583e9ee649bf3acd13bf8c65583e9ee649bf3acd13bf4a51da3ef7bc23bff7bc23bf8c65583e3acd13bf9ee649bfec05d13eec0551bfec05d1be4a51da3ef7bc23bff7bc23bf8c65583e9ee649bf3acd13bfec05d13eec0551bfec05d1bef7bc233ff7bc23bf4a51dabe4a51da3ef7bc23bff7bc23bff7bc233ff7bc23bf4a51dabeec05513fec05d1beec05d1bef7bc233f4a51dabef7bc23bff7bc233f4a51dabef7bc23bf4a51da3ef7bc23bff7bc23bff7bc233ff7bc23bf4a51dabeec05d13eec05d1beec0551bf4a51da3ef7bc23bff7bc23bff7bc233f4a51dabef7bc23bff304353ff30435bf000000009de6493f39cd13bf8b6558be39cd133f9de649bf8b6558be9de6493f39cd13bf8b6558beec05513fec05d1beec05d1bef7bc233ff7bc23bf4a51dabef7bc233ff7bc23bf4a51dabe39cd133f9de649bf8b6558be9de6493f39cd13bf8b6558beec05d13eec0551bfec05d1be39cd133f9de649bf8b6558bef7bc233ff7bc23bf4a51dabe00000000c2c547bebf147bbf4b2f4a3d0bac16bee9e57cbf00000000834016beac3a7dbf4b2f4a3d0bac16bee9e57cbf6fb3c93d6fb3c9bd37817dbf0000000035bdc8bd6dc47ebf00000000834016beac3a7dbf4b2f4a3d0bac16bee9e57cbf0000000035bdc8bd6dc47ebf6fb3c93d6fb3c9bd37817dbf0bac163e4b2f4abde9e57cbfb7314a3db7314abd1b607fbfb7314a3db7314abd1b607fbf0bac163e4b2f4abde9e57cbfc2c5473e00000000bf147bbfb7314a3db7314abd1b607fbfc2c5473e00000000bf147bbf0000000000000000000080bf0000000000000000000080bf0000000030fb48bd10b17fbfb7314a3db7314abd1b607fbfb7314a3db7314abd1b607fbf0000000030fb48bd10b17fbf0000000035bdc8bd6dc47ebfb7314a3db7314abd1b607fbf0000000035bdc8bd6dc47ebf6fb3c93d6fb3c9bd37817dbf0000000015efc3be5e836cbfc873cd3db94196be6c6073bf0000000032a094be0bfa74bfc873cd3db94196be6c6073bfeb864b3eeb864bbec9ad75bf00000000c2c547bebf147bbf0000000032a094be0bfa74bfc873cd3db94196be6c6073bf00000000c2c547bebf147bbfc2c5473e00000000bf147bbf0bac163e4b2f4abde9e57cbf9278493ed7ce4cbd69ab7abf0bac163e4b2f4abde9e57cbf6fb3c93d6fb3c9bd37817dbf28aa4a3e288dccbd4ba179bf9278493ed7ce4cbd69ab7abf0bac163e4b2f4abde9e57cbf28aa4a3e288dccbd4ba179bf6fb3c93d6fb3c9bd37817dbf4b2f4a3d0bac16bee9e57cbf3397183e339718be3b407abf3397183e339718be3b407abf4b2f4a3d0bac16bee9e57cbf00000000c2c547bebf147bbf3397183e339718be3b407abf00000000c2c547bebf147bbfeb864b3eeb864bbec9ad75bfeb864b3eeb864bbec9ad75bfc1594b3ed91719be10f777bf3397183e339718be3b407abf3397183e339718be3b407abfc1594b3ed91719be10f777bf28aa4a3e288dccbd4ba179bf3397183e339718be3b407abf28aa4a3e288dccbd4ba179bf6fb3c93d6fb3c9bd37817dbfeb864b3eeb864bbec9ad75bfdb9c7c3e917c19be451a75bfc1594b3ed91719be10f777bfdb9c7c3e917c19be451a75bfb941963ec873cdbd6c6073bf28aa4a3e288dccbd4ba179bfc1594b3ed91719be10f777bfdb9c7c3e917c19be451a75bf28aa4a3e288dccbd4ba179bfb941963ec873cdbd6c6073bf9480ad3e6fef4dbd4e8270bf85ca7a3e0efc4dbd92de77bf85ca7a3e0efc4dbd92de77bf9480ad3e6fef4dbd4e8270bf15efc33e000000005e836cbf85ca7a3e0efc4dbd92de77bf15efc33e000000005e836cbfc2c5473e00000000bf147bbfc2c5473e00000000bf147bbf9278493ed7ce4cbd69ab7abf85ca7a3e0efc4dbd92de77bf85ca7a3e0efc4dbd92de77bf9278493ed7ce4cbd69ab7abf28aa4a3e288dccbd4ba179bf85ca7a3e0efc4dbd92de77bf28aa4a3e288dccbd4ba179bfb941963ec873cdbd6c6073bf0000000015efc3be5e836cbf00000000da390ebf31db54bfe3db553eb429cfbe04ec63bf00000000da390ebf31db54bf00000000f30435bff30435bf8c65583e3acd13bf9ee649bf8c65583e3acd13bf9ee649bfe3db553eb429cfbe04ec63bf00000000da390ebf31db54bfec05d13eec05d1beec0551bfe3db553eb429cfbe04ec63bf8c65583e3acd13bf9ee649bfb429cf3ee3db55be04ec63bfa8929e3ea8929ebe3b2366bf8947d13e82e09ebed7b75bbfa8929e3ea8929ebe3b2366bfe3db553eb429cfbe04ec63bfec05d13eec05d1beec0551bf8947d13e82e09ebed7b75bbfa8929e3ea8929ebe3b2366bfec05d13eec05d1beec0551bf15efc33e000000005e836cbf9480ad3e6fef4dbd4e8270bf4a99c73e3a6257bd745c6bbf9480ad3e6fef4dbd4e8270bfb941963ec873cdbd6c6073bf90b2ca3e0714d7bda18a69bf4a99c73e3a6257bd745c6bbf9480ad3e6fef4dbd4e8270bf90b2ca3e0714d7bda18a69bfb941963ec873cdbd6c6073bfdb9c7c3e917c19be451a75bf6255b33e52d81ebec4786cbf6255b33e52d81ebec4786cbfdb9c7c3e917c19be451a75bfeb864b3eeb864bbec9ad75bf6255b33e52d81ebec4786cbfeb864b3eeb864bbec9ad75bfb429cf3ee3db55be04ec63bfb429cf3ee3db55be04ec63bfa538cd3e61ed20be370f67bf6255b33e52d81ebec4786cbf6255b33e52d81ebec4786cbfa538cd3e61ed20be370f67bf90b2ca3e0714d7bda18a69bf6255b33e52d81ebec4786cbf90b2ca3e0714d7bda18a69bfb941963ec873cdbd6c6073bfe3db553eb429cfbe04ec63bfa8929e3ea8929ebe3b2366bf0cf0513e6d629bbe76366ebfa8929e3ea8929ebe3b2366bfb429cf3ee3db55be04ec63bfeb864b3eeb864bbec9ad75bf0cf0513e6d629bbe76366ebfa8929e3ea8929ebe3b2366bfeb864b3eeb864bbec9ad75bfeb864b3eeb864bbec9ad75bfc873cd3db94196be6c6073bf0cf0513e6d629bbe76366ebfc873cd3db94196be6c6073bf0000000015efc3be5e836cbfe3db553eb429cfbe04ec63bf0cf0513e6d629bbe76366ebfc873cd3db94196be6c6073bfe3db553eb429cfbe04ec63bf3acd133f8c6558be9ee649bfe2261d3f111d23be28ee45bfc50d133f99db22be7d8e4dbfe2261d3f111d23be28ee45bf46d4253f9243dabdb91c41bf69df113fa6add9bd569b50bfc50d133f99db22be7d8e4dbfe2261d3f111d23be28ee45bf69df113fa6add9bd569b50bf46d4253f9243dabdb91c41bfdecb2d3f76bb5abd97773bbfe2861a3fb2f85abd67a44bbfe2861a3fb2f85abd67a44bbfdecb2d3f76bb5abd97773bbff304353f00000000f30435bfe2861a3fb2f85abd67a44bbff304353f00000000f30435bfda390e3f0000000031db54bfda390e3f0000000031db54bf0843103fdaff59bdd80a53bfe2861a3fb2f85abd67a44bbfe2861a3fb2f85abd67a44bbf0843103fdaff59bdd80a53bf69df113fa6add9bd569b50bfe2861a3fb2f85abd67a44bbf69df113fa6add9bd569b50bf46d4253f9243dabdb91c41bfb429cf3ee3db55be04ec63bfd212e43ea63821be42a061bfa538cd3e61ed20be370f67bfd212e43ea63821be42a061bf94fbf73e62c0d7bde4565ebf90b2ca3e0714d7bda18a69bfa538cd3e61ed20be370f67bfd212e43ea63821be42a061bf90b2ca3e0714d7bda18a69bf94fbf73e62c0d7bde4565ebfcc66053fe83958bd9b135abf22a4de3e4d6a58bdd12166bf22a4de3e4d6a58bdd12166bfcc66053fe83958bd9b135abfda390e3f0000000031db54bf22a4de3e4d6a58bdd12166bfda390e3f0000000031db54bf15efc33e000000005e836cbf15efc33e000000005e836cbf4a99c73e3a6257bd745c6bbf22a4de3e4d6a58bdd12166bf22a4de3e4d6a58bdd12166bf4a99c73e3a6257bd745c6bbf90b2ca3e0714d7bda18a69bf22a4de3e4d6a58bdd12166bf90b2ca3e0714d7bda18a69bf94fbf73e62c0d7bde4565ebfda390e3f0000000031db54bfcc66053fe83958bd9b135abf0843103fdaff59bdd80a53bfcc66053fe83958bd9b135abf94fbf73e62c0d7bde4565ebf69df113fa6add9bd569b50bf0843103fdaff59bdd80a53bfcc66053fe83958bd9b135abf69df113fa6add9bd569b50bf94fbf73e62c0d7bde4565ebfd212e43ea63821be42a061bf4158083fc5ab22bedcd154bf4158083fc5ab22bedcd154bfd212e43ea63821be42a061bfb429cf3ee3db55be04ec63bf4158083fc5ab22bedcd154bfb429cf3ee3db55be04ec63bf3acd133f8c6558be9ee649bf3acd133f8c6558be9ee649bfc50d133f99db22be7d8e4dbf4158083fc5ab22bedcd154bf4158083fc5ab22bedcd154bfc50d133f99db22be7d8e4dbf69df113fa6add9bd569b50bf4158083fc5ab22bedcd154bf69df113fa6add9bd569b50bf94fbf73e62c0d7bde4565ebfec05d13eec05d1beec0551bf8d7dfe3eb8fa9fbe233c4fbf8947d13e82e09ebed7b75bbf8d7dfe3eb8fa9fbe233c4fbf3acd133f8c6558be9ee649bfb429cf3ee3db55be04ec63bf8947d13e82e09ebed7b75bbf8d7dfe3eb8fa9fbe233c4fbfb429cf3ee3db55be04ec63bf00000000000080bf0000000000000000bf147bbfc2c5473ec2c5473ebf147bbf0000000000000000bf147bbfc2c5473e000000005e836cbf15efc33eea864b3ec8ad75bfea864b3eea864b3ec8ad75bfea864b3ec2c5473ebf147bbf0000000000000000bf147bbfc2c5473e15efc33e5e836cbf00000000c2c5473ebf147bbf00000000ea864b3ec8ad75bfea864b3e000000005e836cbf15efc33e0000000031db54bfda390e3fe3db553e04ec63bfb429cf3e0000000031db54bfda390e3f00000000f30435bff304353f8c65583e9ee649bf3acd133f8c65583e9ee649bf3acd133fe3db553e04ec63bfb429cf3e0000000031db54bfda390e3fec05d13eec0551bfec05d13ee3db553e04ec63bfb429cf3e8c65583e9ee649bf3acd133fec05d13eec0551bfec05d13eb429cf3e04ec63bfe3db553ee3db553e04ec63bfb429cf3eb429cf3e04ec63bfe3db553e15efc33e5e836cbf00000000ea864b3ec8ad75bfea864b3eea864b3ec8ad75bfea864b3ee3db553e04ec63bfb429cf3eb429cf3e04ec63bfe3db553e000000005e836cbf15efc33ee3db553e04ec63bfb429cf3eea864b3ec8ad75bfea864b3ef304353ff30435bf00000000da390e3f31db54bf0000000039cd133f9de649bf8b65583eda390e3f31db54bf0000000015efc33e5e836cbf00000000b429cf3e04ec63bfe3db553eb429cf3e04ec63bfe3db553e39cd133f9de649bf8b65583eda390e3f31db54bf00000000ec05d13eec0551bfec05d13e39cd133f9de649bf8b65583eb429cf3e04ec63bfe3db553e00000000f30435bff304353f00000000da390ebf31db543f8c65583e3acd13bf9ee6493f00000000da390ebf31db543f0000000015efc3be5e836c3fe3db553eb429cfbe04ec633fe3db553eb429cfbe04ec633f8c65583e3acd13bf9ee6493f00000000da390ebf31db543fec05d13eec05d1beec05513f8c65583e3acd13bf9ee6493fe3db553eb429cfbe04ec633f0000000015efc3be5e836c3f0000000032a094be0bfa743fc873cd3db94196be6c60733fc873cd3db94196be6c60733f0000000032a094be0bfa743f00000000c2c547bebf147b3fc873cd3db94196be6c60733f00000000c2c547bebf147b3feb864b3eeb864bbec9ad753f00000000c2c547bebf147b3f00000000834016beac3a7d3f4b2f4a3d0bac16bee9e57c3f4b2f4a3d0bac16bee9e57c3f00000000834016beac3a7d3f0000000035bdc8bd6dc47e3f4b2f4a3d0bac16bee9e57c3f0000000035bdc8bd6dc47e3f6fb3c93d6fb3c9bd37817d3f00000000000000000000803fb7314a3db7314abd1b607f3f0000000030fb48bd10b17f3fb7314a3db7314abd1b607f3f6fb3c93d6fb3c9bd37817d3f0000000035bdc8bd6dc47e3f0000000030fb48bd10b17f3fb7314a3db7314abd1b607f3f0000000035bdc8bd6dc47e3f6fb3c93d6fb3c9bd37817d3fb7314a3db7314abd1b607f3f0bac163e4b2f4abde9e57c3fb7314a3db7314abd1b607f3f00000000000000000000803fc2c5473e00000000bf147b3f0bac163e4b2f4abde9e57c3fb7314a3db7314abd1b607f3fc2c5473e00000000bf147b3fc2c5473e00000000bf147b3f9278493ed7ce4cbd69ab7a3f0bac163e4b2f4abde9e57c3f0bac163e4b2f4abde9e57c3f9278493ed7ce4cbd69ab7a3f28aa4a3e288dccbd4ba1793f0bac163e4b2f4abde9e57c3f28aa4a3e288dccbd4ba1793f6fb3c93d6fb3c9bd37817d3feb864b3eeb864bbec9ad753f3397183e339718be3b407a3fc1594b3ed91719be10f7773f3397183e339718be3b407a3f6fb3c93d6fb3c9bd37817d3f28aa4a3e288dccbd4ba1793fc1594b3ed91719be10f7773f3397183e339718be3b407a3f28aa4a3e288dccbd4ba1793f6fb3c93d6fb3c9bd37817d3f3397183e339718be3b407a3f4b2f4a3d0bac16bee9e57c3f3397183e339718be3b407a3feb864b3eeb864bbec9ad753f00000000c2c547bebf147b3f4b2f4a3d0bac16bee9e57c3f3397183e339718be3b407a3f00000000c2c547bebf147b3feb864b3eeb864bbec9ad753fc1594b3ed91719be10f7773fdb9c7c3e917c19be451a753fdb9c7c3e917c19be451a753fc1594b3ed91719be10f7773f28aa4a3e288dccbd4ba1793fdb9c7c3e917c19be451a753f28aa4a3e288dccbd4ba1793fb941963ec873cdbd6c60733fc2c5473e00000000bf147b3f85ca7a3e0efc4dbd92de773f9278493ed7ce4cbd69ab7a3f85ca7a3e0efc4dbd92de773fb941963ec873cdbd6c60733f28aa4a3e288dccbd4ba1793f9278493ed7ce4cbd69ab7a3f85ca7a3e0efc4dbd92de773f28aa4a3e288dccbd4ba1793fb941963ec873cdbd6c60733f85ca7a3e0efc4dbd92de773f9480ad3e6fef4dbd4e82703f85ca7a3e0efc4dbd92de773fc2c5473e00000000bf147b3f15efc33e000000005e836c3f9480ad3e6fef4dbd4e82703f85ca7a3e0efc4dbd92de773f15efc33e000000005e836c3f15efc33e000000005e836c3f4a99c73e3a6257bd745c6b3f9480ad3e6fef4dbd4e82703f9480ad3e6fef4dbd4e82703f4a99c73e3a6257bd745c6b3f90b2ca3e0714d7bda18a693f9480ad3e6fef4dbd4e82703f90b2ca3e0714d7bda18a693fb941963ec873cdbd6c60733fb429cf3ee3db55be04ec633f6255b33e52d81ebec4786c3fa538cd3e61ed20be370f673f6255b33e52d81ebec4786c3fb941963ec873cdbd6c60733f90b2ca3e0714d7bda18a693fa538cd3e61ed20be370f673f6255b33e52d81ebec4786c3f90b2ca3e0714d7bda18a693fb941963ec873cdbd6c60733f6255b33e52d81ebec4786c3fdb9c7c3e917c19be451a753f6255b33e52d81ebec4786c3fb429cf3ee3db55be04ec633feb864b3eeb864bbec9ad753fdb9c7c3e917c19be451a753f6255b33e52d81ebec4786c3feb864b3eeb864bbec9ad753fb429cf3ee3db55be04ec633f8947d13e82e09ebed7b75b3fa8929e3ea8929ebe3b23663fa8929e3ea8929ebe3b23663f8947d13e82e09ebed7b75b3fec05d13eec05d1beec05513fa8929e3ea8929ebe3b23663fec05d13eec05d1beec05513fe3db553eb429cfbe04ec633fe3db553eb429cfbe04ec633f0cf0513e6d629bbe76366e3fa8929e3ea8929ebe3b23663fa8929e3ea8929ebe3b23663f0cf0513e6d629bbe76366e3feb864b3eeb864bbec9ad753fa8929e3ea8929ebe3b23663feb864b3eeb864bbec9ad753fb429cf3ee3db55be04ec633feb864b3eeb864bbec9ad753f0cf0513e6d629bbe76366e3fc873cd3db94196be6c60733fc873cd3db94196be6c60733f0cf0513e6d629bbe76366e3fe3db553eb429cfbe04ec633fc873cd3db94196be6c60733fe3db553eb429cfbe04ec633f0000000015efc3be5e836c3f3acd133f8c6558be9ee6493fc50d133f99db22be7d8e4d3fe2261d3f111d23be28ee453fe2261d3f111d23be28ee453fc50d133f99db22be7d8e4d3f69df113fa6add9bd569b503fe2261d3f111d23be28ee453f69df113fa6add9bd569b503f46d4253f9243dabdb91c413fda390e3f0000000031db543fe2861a3fb2f85abd67a44b3f0843103fdaff59bdd80a533fe2861a3fb2f85abd67a44b3f46d4253f9243dabdb91c413f69df113fa6add9bd569b503f0843103fdaff59bdd80a533fe2861a3fb2f85abd67a44b3f69df113fa6add9bd569b503f46d4253f9243dabdb91c413fe2861a3fb2f85abd67a44b3fdecb2d3f76bb5abd97773b3fe2861a3fb2f85abd67a44b3fda390e3f0000000031db543ff304353f00000000f304353fdecb2d3f76bb5abd97773b3fe2861a3fb2f85abd67a44b3ff304353f00000000f304353fec05d13eec05d1beec05513f8947d13e82e09ebed7b75b3f8d7dfe3eb8fa9fbe233c4f3f8d7dfe3eb8fa9fbe233c4f3f8947d13e82e09ebed7b75b3fb429cf3ee3db55be04ec633f8d7dfe3eb8fa9fbe233c4f3fb429cf3ee3db55be04ec633f3acd133f8c6558be9ee6493fda390e3f0000000031db543f0843103fdaff59bdd80a533fcc66053fe83958bd9b135a3fcc66053fe83958bd9b135a3f0843103fdaff59bdd80a533f69df113fa6add9bd569b503fcc66053fe83958bd9b135a3f69df113fa6add9bd569b503f94fbf73e62c0d7bde4565e3f3acd133f8c6558be9ee6493f4158083fc5ab22bedcd1543fc50d133f99db22be7d8e4d3f4158083fc5ab22bedcd1543f94fbf73e62c0d7bde4565e3f69df113fa6add9bd569b503fc50d133f99db22be7d8e4d3f4158083fc5ab22bedcd1543f69df113fa6add9bd569b503f94fbf73e62c0d7bde4565e3f4158083fc5ab22bedcd1543fd212e43ea63821be42a0613f4158083fc5ab22bedcd1543f3acd133f8c6558be9ee6493fb429cf3ee3db55be04ec633fd212e43ea63821be42a0613f4158083fc5ab22bedcd1543fb429cf3ee3db55be04ec633fb429cf3ee3db55be04ec633fa538cd3e61ed20be370f673fd212e43ea63821be42a0613fd212e43ea63821be42a0613fa538cd3e61ed20be370f673f90b2ca3e0714d7bda18a693fd212e43ea63821be42a0613f90b2ca3e0714d7bda18a693f94fbf73e62c0d7bde4565e3f15efc33e000000005e836c3f22a4de3e4d6a58bdd121663f4a99c73e3a6257bd745c6b3f22a4de3e4d6a58bdd121663f94fbf73e62c0d7bde4565e3f90b2ca3e0714d7bda18a693f4a99c73e3a6257bd745c6b3f22a4de3e4d6a58bdd121663f90b2ca3e0714d7bda18a693f94fbf73e62c0d7bde4565e3f22a4de3e4d6a58bdd121663fcc66053fe83958bd9b135a3f22a4de3e4d6a58bdd121663f15efc33e000000005e836c3fda390e3f0000000031db543fcc66053fe83958bd9b135a3f22a4de3e4d6a58bdd121663fda390e3f0000000031db543ff304353f00000000f304353f97773b3f76bb5abddecb2d3fdecb2d3f76bb5abd97773b3f97773b3f76bb5abddecb2d3fb91c413f9243dabd46d4253f46d4253f9243dabdb91c413fdecb2d3f76bb5abd97773b3f97773b3f76bb5abddecb2d3f46d4253f9243dabdb91c413f9ee6493f8c6558be3acd133f744f393f350b24be00cc2b3f28ee453f111d23bee2261d3f744f393f350b24be00cc2b3f46d4253f9243dabdb91c413fb91c413f9243dabd46d4253f28ee453f111d23bee2261d3f744f393f350b24be00cc2b3fb91c413f9243dabd46d4253f46d4253f9243dabdb91c413f744f393f350b24be00cc2b3fe2261d3f111d23be28ee453f744f393f350b24be00cc2b3f9ee6493f8c6558be3acd133f3acd133f8c6558be9ee6493fe2261d3f111d23be28ee453f744f393f350b24be00cc2b3f3acd133f8c6558be9ee6493f9ee6493f8c6558be3acd133f233c4f3fb8fa9fbe8d7dfe3eeb7e383f3fc1a4bebb321d3feb7e383f3fc1a4bebb321d3f233c4f3fb8fa9fbe8d7dfe3eec05513fec05d1beec05d13eeb7e383f3fc1a4bebb321d3fec05513fec05d1beec05d13ef7bc233f4a51dabef7bc233ff7bc233f4a51dabef7bc233fbb321d3f3fc1a4beeb7e383feb7e383f3fc1a4bebb321d3feb7e383f3fc1a4bebb321d3fbb321d3f3fc1a4beeb7e383f3acd133f8c6558be9ee6493feb7e383f3fc1a4bebb321d3f3acd133f8c6558be9ee6493f9ee6493f8c6558be3acd133f3acd133f8c6558be9ee6493fbb321d3f3fc1a4beeb7e383f8d7dfe3eb8fa9fbe233c4f3fbb321d3f3fc1a4beeb7e383ff7bc233f4a51dabef7bc233fec05d13eec05d1beec05513f8d7dfe3eb8fa9fbe233c4f3fbb321d3f3fc1a4beeb7e383fec05d13eec05d1beec05513fec05513fec05d1beec05d13e9de6493f39cd13bf8b65583ef7bc233ff7bc23bf4a51da3e9de6493f39cd13bf8b65583ef304353ff30435bf0000000039cd133f9de649bf8b65583e39cd133f9de649bf8b65583ef7bc233ff7bc23bf4a51da3e9de6493f39cd13bf8b65583eec05d13eec0551bfec05d13ef7bc233ff7bc23bf4a51da3e39cd133f9de649bf8b65583eec05d13eec0551bfec05d13e4a51da3ef7bc23bff7bc233ff7bc233ff7bc23bf4a51da3e4a51da3ef7bc23bff7bc233fec05d13eec05d1beec05513ff7bc233f4a51dabef7bc233ff7bc233f4a51dabef7bc233ff7bc233ff7bc23bf4a51da3e4a51da3ef7bc23bff7bc233fec05513fec05d1beec05d13ef7bc233ff7bc23bf4a51da3ef7bc233f4a51dabef7bc233f00000000f30435bff304353f8c65583e3acd13bf9ee6493f8c65583e9ee649bf3acd133f8c65583e3acd13bf9ee6493fec05d13eec05d1beec05513f4a51da3ef7bc23bff7bc233f4a51da3ef7bc23bff7bc233f8c65583e9ee649bf3acd133f8c65583e3acd13bf9ee6493fec05d13eec0551bfec05d13e8c65583e9ee649bf3acd133f4a51da3ef7bc23bff7bc233fbf147b3fc2c547be00000000e9e57c3f0bac16be4b2f4a3dac3a7d3f834016be00000000e9e57c3f0bac16be4b2f4a3d37817d3f6fb3c9bd6fb3c93d6dc47e3f35bdc8bd00000000ac3a7d3f834016be00000000e9e57c3f0bac16be4b2f4a3d6dc47e3f35bdc8bd0000000037817d3f6fb3c9bd6fb3c93de9e57c3f4b2f4abd0bac163e1c607f3fb8314abdb8314a3d1c607f3fb8314abdb8314a3de9e57c3f4b2f4abd0bac163ebf147b3f00000000c2c5473e1c607f3fb8314abdb8314a3dbf147b3f00000000c2c5473e0000803f00000000000000000000803f000000000000000010b17f3f30fb48bd000000001c607f3fb8314abdb8314a3d1c607f3fb8314abdb8314a3d10b17f3f30fb48bd000000006dc47e3f35bdc8bd000000001c607f3fb8314abdb8314a3d6dc47e3f35bdc8bd0000000037817d3f6fb3c9bd6fb3c93d5e836c3f15efc3be000000006c60733fba4196bec873cd3d0bfa743f32a094be000000006c60733fba4196bec873cd3dc8ad753fea864bbeea864b3ebf147b3fc2c547be000000000bfa743f32a094be000000006c60733fba4196bec873cd3dbf147b3fc2c547be00000000bf147b3f00000000c2c5473ee9e57c3f4b2f4abd0bac163e69ab7a3fd6ce4cbd9278493ee9e57c3f4b2f4abd0bac163e37817d3f6fb3c9bd6fb3c93d4ba1793f278dccbd28aa4a3e69ab7a3fd6ce4cbd9278493ee9e57c3f4b2f4abd0bac163e4ba1793f278dccbd28aa4a3e37817d3f6fb3c9bd6fb3c93de9e57c3f0bac16be4b2f4a3d3b407a3f339718be3397183e3b407a3f339718be3397183ee9e57c3f0bac16be4b2f4a3dbf147b3fc2c547be000000003b407a3f339718be3397183ebf147b3fc2c547be00000000c8ad753fea864bbeea864b3ec8ad753fea864bbeea864b3e10f7773fd81719bec0594b3e3b407a3f339718be3397183e3b407a3f339718be3397183e10f7773fd81719bec0594b3e4ba1793f278dccbd28aa4a3e3b407a3f339718be3397183e4ba1793f278dccbd28aa4a3e37817d3f6fb3c9bd6fb3c93dc8ad753fea864bbeea864b3e451a753f907c19bedc9c7c3e10f7773fd81719bec0594b3e451a753f907c19bedc9c7c3e6c60733fc873cdbdba41963e4ba1793f278dccbd28aa4a3e10f7773fd81719bec0594b3e451a753f907c19bedc9c7c3e4ba1793f278dccbd28aa4a3e6c60733fc873cdbdba41963e4e82703f6fef4dbd9580ad3e92de773f0efc4dbd86ca7a3e92de773f0efc4dbd86ca7a3e4e82703f6fef4dbd9580ad3e5e836c3f0000000015efc33e92de773f0efc4dbd86ca7a3e5e836c3f0000000015efc33ebf147b3f00000000c2c5473ebf147b3f00000000c2c5473e69ab7a3fd6ce4cbd9278493e92de773f0efc4dbd86ca7a3e92de773f0efc4dbd86ca7a3e69ab7a3fd6ce4cbd9278493e4ba1793f278dccbd28aa4a3e92de773f0efc4dbd86ca7a3e4ba1793f278dccbd28aa4a3e6c60733fc873cdbdba41963e5e836c3f15efc3be0000000031db543fda390ebf0000000004ec633fb429cfbee3db553e31db543fda390ebf00000000f304353ff30435bf000000009de6493f39cd13bf8b65583e9de6493f39cd13bf8b65583e04ec633fb429cfbee3db553e31db543fda390ebf00000000ec05513fec05d1beec05d13e04ec633fb429cfbee3db553e9de6493f39cd13bf8b65583e04ec633fe3db55beb429cf3e3b23663fa8929ebea8929e3ed7b75b3f82e09ebe8947d13e3b23663fa8929ebea8929e3e04ec633fb429cfbee3db553eec05513fec05d1beec05d13ed7b75b3f82e09ebe8947d13e3b23663fa8929ebea8929e3eec05513fec05d1beec05d13e5e836c3f0000000015efc33e4e82703f6fef4dbd9580ad3e745c6b3f3a6257bd4a99c73e4e82703f6fef4dbd9580ad3e6c60733fc873cdbdba41963ea18a693f0714d7bd90b2ca3e745c6b3f3a6257bd4a99c73e4e82703f6fef4dbd9580ad3ea18a693f0714d7bd90b2ca3e6c60733fc873cdbdba41963e451a753f907c19bedc9c7c3ec4786c3f52d81ebe6355b33ec4786c3f52d81ebe6355b33e451a753f907c19bedc9c7c3ec8ad753fea864bbeea864b3ec4786c3f52d81ebe6355b33ec8ad753fea864bbeea864b3e04ec633fe3db55beb429cf3e04ec633fe3db55beb429cf3e370f673f61ed20bea538cd3ec4786c3f52d81ebe6355b33ec4786c3f52d81ebe6355b33e370f673f61ed20bea538cd3ea18a693f0714d7bd90b2ca3ec4786c3f52d81ebe6355b33ea18a693f0714d7bd90b2ca3e6c60733fc873cdbdba41963e04ec633fb429cfbee3db553e3b23663fa8929ebea8929e3e76366e3f6c629bbe0bf0513e3b23663fa8929ebea8929e3e04ec633fe3db55beb429cf3ec8ad753fea864bbeea864b3e76366e3f6c629bbe0bf0513e3b23663fa8929ebea8929e3ec8ad753fea864bbeea864b3ec8ad753fea864bbeea864b3e6c60733fba4196bec873cd3d76366e3f6c629bbe0bf0513e6c60733fba4196bec873cd3d5e836c3f15efc3be0000000004ec633fb429cfbee3db553e76366e3f6c629bbe0bf0513e6c60733fba4196bec873cd3d04ec633fb429cfbee3db553e9ee6493f8c6558be3acd133f28ee453f111d23bee2261d3f7d8e4d3f9adb22bec60d133f28ee453f111d23bee2261d3fb91c413f9243dabd46d4253f569b503fa6add9bd69df113f7d8e4d3f9adb22bec60d133f28ee453f111d23bee2261d3f569b503fa6add9bd69df113fb91c413f9243dabd46d4253f97773b3f76bb5abddecb2d3f67a44b3fb2f85abde2861a3f67a44b3fb2f85abde2861a3f97773b3f76bb5abddecb2d3ff304353f00000000f304353f67a44b3fb2f85abde2861a3ff304353f00000000f304353f31db543f00000000da390e3f31db543f00000000da390e3fd80a533fdaff59bd0843103f67a44b3fb2f85abde2861a3f67a44b3fb2f85abde2861a3fd80a533fdaff59bd0843103f569b503fa6add9bd69df113f67a44b3fb2f85abde2861a3f569b503fa6add9bd69df113fb91c413f9243dabd46d4253f04ec633fe3db55beb429cf3e42a0613fa63821bed212e43e370f673f61ed20bea538cd3e42a0613fa63821bed212e43ee4565e3f62c0d7bd94fbf73ea18a693f0714d7bd90b2ca3e370f673f61ed20bea538cd3e42a0613fa63821bed212e43ea18a693f0714d7bd90b2ca3ee4565e3f62c0d7bd94fbf73e9b135a3fe83958bdcc66053fd121663f4d6a58bd22a4de3ed121663f4d6a58bd22a4de3e9b135a3fe83958bdcc66053f31db543f00000000da390e3fd121663f4d6a58bd22a4de3e31db543f00000000da390e3f5e836c3f0000000015efc33e5e836c3f0000000015efc33e745c6b3f3a6257bd4a99c73ed121663f4d6a58bd22a4de3ed121663f4d6a58bd22a4de3e745c6b3f3a6257bd4a99c73ea18a693f0714d7bd90b2ca3ed121663f4d6a58bd22a4de3ea18a693f0714d7bd90b2ca3ee4565e3f62c0d7bd94fbf73e31db543f00000000da390e3f9b135a3fe83958bdcc66053fd80a533fdaff59bd0843103f9b135a3fe83958bdcc66053fe4565e3f62c0d7bd94fbf73e569b503fa6add9bd69df113fd80a533fdaff59bd0843103f9b135a3fe83958bdcc66053f569b503fa6add9bd69df113fe4565e3f62c0d7bd94fbf73e42a0613fa63821bed212e43edcd1543fc5ab22be4158083fdcd1543fc5ab22be4158083f42a0613fa63821bed212e43e04ec633fe3db55beb429cf3edcd1543fc5ab22be4158083f04ec633fe3db55beb429cf3e9ee6493f8c6558be3acd133f9ee6493f8c6558be3acd133f7d8e4d3f9adb22bec60d133fdcd1543fc5ab22be4158083fdcd1543fc5ab22be4158083f7d8e4d3f9adb22bec60d133f569b503fa6add9bd69df113fdcd1543fc5ab22be4158083f569b503fa6add9bd69df113fe4565e3f62c0d7bd94fbf73eec05513fec05d1beec05d13e233c4f3fb8fa9fbe8d7dfe3ed7b75b3f82e09ebe8947d13e233c4f3fb8fa9fbe8d7dfe3e9ee6493f8c6558be3acd133f04ec633fe3db55beb429cf3ed7b75b3f82e09ebe8947d13e233c4f3fb8fa9fbe8d7dfe3e04ec633fe3db55beb429cf3e00000000000080bf00000000c2c547bebf147bbf0000000000000000bf147bbfc2c5473ec2c547bebf147bbf0000000015efc3be5e836cbf00000000ea864bbec8ad75bfea864b3eea864bbec8ad75bfea864b3e00000000bf147bbfc2c5473ec2c547bebf147bbf00000000000000005e836cbf15efc33e00000000bf147bbfc2c5473eea864bbec8ad75bfea864b3e15efc3be5e836cbf00000000da390ebf31db54bf00000000b429cfbe04ec63bfe3db553eda390ebf31db54bf00000000f30435bff30435bf0000000039cd13bf9de649bf8b65583e39cd13bf9de649bf8b65583eb429cfbe04ec63bfe3db553eda390ebf31db54bf00000000ec05d1beec0551bfec05d13eb429cfbe04ec63bfe3db553e39cd13bf9de649bf8b65583eec05d1beec0551bfec05d13ee3db55be04ec63bfb429cf3eb429cfbe04ec63bfe3db553ee3db55be04ec63bfb429cf3e000000005e836cbf15efc33eea864bbec8ad75bfea864b3eea864bbec8ad75bfea864b3eb429cfbe04ec63bfe3db553ee3db55be04ec63bfb429cf3e15efc3be5e836cbf00000000b429cfbe04ec63bfe3db553eea864bbec8ad75bfea864b3e00000000f30435bff304353f0000000031db54bfda390e3f8c6558be9ee649bf3acd133f0000000031db54bfda390e3f000000005e836cbf15efc33ee3db55be04ec63bfb429cf3ee3db55be04ec63bfb429cf3e8c6558be9ee649bf3acd133f0000000031db54bfda390e3fec05d1beec0551bfec05d13e8c6558be9ee649bf3acd133fe3db55be04ec63bfb429cf3ef30435bff30435bf0000000031db54bfda390ebf000000009de649bf39cd13bf8b65583e31db54bfda390ebf000000005e836cbf15efc3be0000000004ec63bfb429cfbee3db553e04ec63bfb429cfbee3db553e9de649bf39cd13bf8b65583e31db54bfda390ebf00000000ec0551bfec05d1beec05d13e9de649bf39cd13bf8b65583e04ec63bfb429cfbee3db553e5e836cbf15efc3be000000000bfa74bf32a094be000000006c6073bfba4196bec873cd3d6c6073bfba4196bec873cd3d0bfa74bf32a094be00000000bf147bbfc2c547be000000006c6073bfba4196bec873cd3dbf147bbfc2c547be00000000c8ad75bfea864bbeea864b3ebf147bbfc2c547be00000000ac3a7dbf834016be00000000e9e57cbf0bac16be4b2f4a3de9e57cbf0bac16be4b2f4a3dac3a7dbf834016be000000006dc47ebf35bdc8bd00000000e9e57cbf0bac16be4b2f4a3d6dc47ebf35bdc8bd0000000037817dbf6fb3c9bd6fb3c93d000080bf00000000000000001c607fbfb8314abdb8314a3d10b17fbf30fb48bd000000001c607fbfb8314abdb8314a3d37817dbf6fb3c9bd6fb3c93d6dc47ebf35bdc8bd0000000010b17fbf30fb48bd000000001c607fbfb8314abdb8314a3d6dc47ebf35bdc8bd0000000037817dbf6fb3c9bd6fb3c93d1c607fbfb8314abdb8314a3de9e57cbf4b2f4abd0bac163e1c607fbfb8314abdb8314a3d000080bf0000000000000000bf147bbf00000000c2c5473ee9e57cbf4b2f4abd0bac163e1c607fbfb8314abdb8314a3dbf147bbf00000000c2c5473ebf147bbf00000000c2c5473e69ab7abfd6ce4cbd9278493ee9e57cbf4b2f4abd0bac163ee9e57cbf4b2f4abd0bac163e69ab7abfd6ce4cbd9278493e4ba179bf278dccbd28aa4a3ee9e57cbf4b2f4abd0bac163e4ba179bf278dccbd28aa4a3e37817dbf6fb3c9bd6fb3c93dc8ad75bfea864bbeea864b3e3b407abf339718be3397183e10f777bfd81719bec0594b3e3b407abf339718be3397183e37817dbf6fb3c9bd6fb3c93d4ba179bf278dccbd28aa4a3e10f777bfd81719bec0594b3e3b407abf339718be3397183e4ba179bf278dccbd28aa4a3e37817dbf6fb3c9bd6fb3c93d3b407abf339718be3397183ee9e57cbf0bac16be4b2f4a3d3b407abf339718be3397183ec8ad75bfea864bbeea864b3ebf147bbfc2c547be00000000e9e57cbf0bac16be4b2f4a3d3b407abf339718be3397183ebf147bbfc2c547be00000000c8ad75bfea864bbeea864b3e10f777bfd81719bec0594b3e451a75bf907c19bedc9c7c3e451a75bf907c19bedc9c7c3e10f777bfd81719bec0594b3e4ba179bf278dccbd28aa4a3e451a75bf907c19bedc9c7c3e4ba179bf278dccbd28aa4a3e6c6073bfc873cdbdba41963ebf147bbf00000000c2c5473e92de77bf0efc4dbd86ca7a3e69ab7abfd6ce4cbd9278493e92de77bf0efc4dbd86ca7a3e6c6073bfc873cdbdba41963e4ba179bf278dccbd28aa4a3e69ab7abfd6ce4cbd9278493e92de77bf0efc4dbd86ca7a3e4ba179bf278dccbd28aa4a3e6c6073bfc873cdbdba41963e92de77bf0efc4dbd86ca7a3e4e8270bf6fef4dbd9580ad3e92de77bf0efc4dbd86ca7a3ebf147bbf00000000c2c5473e5e836cbf0000000015efc33e4e8270bf6fef4dbd9580ad3e92de77bf0efc4dbd86ca7a3e5e836cbf0000000015efc33e5e836cbf0000000015efc33e745c6bbf3a6257bd4a99c73e4e8270bf6fef4dbd9580ad3e4e8270bf6fef4dbd9580ad3e745c6bbf3a6257bd4a99c73ea18a69bf0714d7bd90b2ca3e4e8270bf6fef4dbd9580ad3ea18a69bf0714d7bd90b2ca3e6c6073bfc873cdbdba41963e04ec63bfe3db55beb429cf3ec4786cbf52d81ebe6355b33e370f67bf61ed20bea538cd3ec4786cbf52d81ebe6355b33e6c6073bfc873cdbdba41963ea18a69bf0714d7bd90b2ca3e370f67bf61ed20bea538cd3ec4786cbf52d81ebe6355b33ea18a69bf0714d7bd90b2ca3e6c6073bfc873cdbdba41963ec4786cbf52d81ebe6355b33e451a75bf907c19bedc9c7c3ec4786cbf52d81ebe6355b33e04ec63bfe3db55beb429cf3ec8ad75bfea864bbeea864b3e451a75bf907c19bedc9c7c3ec4786cbf52d81ebe6355b33ec8ad75bfea864bbeea864b3e04ec63bfe3db55beb429cf3ed7b75bbf82e09ebe8947d13e3b2366bfa8929ebea8929e3e3b2366bfa8929ebea8929e3ed7b75bbf82e09ebe8947d13eec0551bfec05d1beec05d13e3b2366bfa8929ebea8929e3eec0551bfec05d1beec05d13e04ec63bfb429cfbee3db553e04ec63bfb429cfbee3db553e76366ebf6c629bbe0bf0513e3b2366bfa8929ebea8929e3e3b2366bfa8929ebea8929e3e76366ebf6c629bbe0bf0513ec8ad75bfea864bbeea864b3e3b2366bfa8929ebea8929e3ec8ad75bfea864bbeea864b3e04ec63bfe3db55beb429cf3ec8ad75bfea864bbeea864b3e76366ebf6c629bbe0bf0513e6c6073bfba4196bec873cd3d6c6073bfba4196bec873cd3d76366ebf6c629bbe0bf0513e04ec63bfb429cfbee3db553e6c6073bfba4196bec873cd3d04ec63bfb429cfbee3db553e5e836cbf15efc3be000000009ee649bf8c6558be3acd133f7d8e4dbf9adb22bec60d133f28ee45bf111d23bee2261d3f28ee45bf111d23bee2261d3f7d8e4dbf9adb22bec60d133f569b50bfa6add9bd69df113f28ee45bf111d23bee2261d3f569b50bfa6add9bd69df113fb91c41bf9243dabd46d4253f31db54bf00000000da390e3f67a44bbfb2f85abde2861a3fd80a53bfdaff59bd0843103f67a44bbfb2f85abde2861a3fb91c41bf9243dabd46d4253f569b50bfa6add9bd69df113fd80a53bfdaff59bd0843103f67a44bbfb2f85abde2861a3f569b50bfa6add9bd69df113fb91c41bf9243dabd46d4253f67a44bbfb2f85abde2861a3f97773bbf76bb5abddecb2d3f67a44bbfb2f85abde2861a3f31db54bf00000000da390e3ff30435bf00000000f304353f97773bbf76bb5abddecb2d3f67a44bbfb2f85abde2861a3ff30435bf00000000f304353fec0551bfec05d1beec05d13ed7b75bbf82e09ebe8947d13e233c4fbfb8fa9fbe8d7dfe3e233c4fbfb8fa9fbe8d7dfe3ed7b75bbf82e09ebe8947d13e04ec63bfe3db55beb429cf3e233c4fbfb8fa9fbe8d7dfe3e04ec63bfe3db55beb429cf3e9ee649bf8c6558be3acd133f31db54bf00000000da390e3fd80a53bfdaff59bd0843103f9b135abfe83958bdcc66053f9b135abfe83958bdcc66053fd80a53bfdaff59bd0843103f569b50bfa6add9bd69df113f9b135abfe83958bdcc66053f569b50bfa6add9bd69df113fe4565ebf62c0d7bd94fbf73e9ee649bf8c6558be3acd133fdcd154bfc5ab22be4158083f7d8e4dbf9adb22bec60d133fdcd154bfc5ab22be4158083fe4565ebf62c0d7bd94fbf73e569b50bfa6add9bd69df113f7d8e4dbf9adb22bec60d133fdcd154bfc5ab22be4158083f569b50bfa6add9bd69df113fe4565ebf62c0d7bd94fbf73edcd154bfc5ab22be4158083f42a061bfa63821bed212e43edcd154bfc5ab22be4158083f9ee649bf8c6558be3acd133f04ec63bfe3db55beb429cf3e42a061bfa63821bed212e43edcd154bfc5ab22be4158083f04ec63bfe3db55beb429cf3e04ec63bfe3db55beb429cf3e370f67bf61ed20bea538cd3e42a061bfa63821bed212e43e42a061bfa63821bed212e43e370f67bf61ed20bea538cd3ea18a69bf0714d7bd90b2ca3e42a061bfa63821bed212e43ea18a69bf0714d7bd90b2ca3ee4565ebf62c0d7bd94fbf73e5e836cbf0000000015efc33ed12166bf4d6a58bd22a4de3e745c6bbf3a6257bd4a99c73ed12166bf4d6a58bd22a4de3ee4565ebf62c0d7bd94fbf73ea18a69bf0714d7bd90b2ca3e745c6bbf3a6257bd4a99c73ed12166bf4d6a58bd22a4de3ea18a69bf0714d7bd90b2ca3ee4565ebf62c0d7bd94fbf73ed12166bf4d6a58bd22a4de3e9b135abfe83958bdcc66053fd12166bf4d6a58bd22a4de3e5e836cbf0000000015efc33e31db54bf00000000da390e3f9b135abfe83958bdcc66053fd12166bf4d6a58bd22a4de3e31db54bf00000000da390e3ff30435bf00000000f304353fdecb2dbf76bb5abd97773b3f97773bbf76bb5abddecb2d3f97773bbf76bb5abddecb2d3fdecb2dbf76bb5abd97773b3f46d425bf9243dabdb91c413f97773bbf76bb5abddecb2d3f46d425bf9243dabdb91c413fb91c41bf9243dabd46d4253f3acd13bf8c6558be9ee6493f00cc2bbf350b24be744f393fe2261dbf111d23be28ee453f00cc2bbf350b24be744f393fb91c41bf9243dabd46d4253f46d425bf9243dabdb91c413fe2261dbf111d23be28ee453f00cc2bbf350b24be744f393f46d425bf9243dabdb91c413fb91c41bf9243dabd46d4253f00cc2bbf350b24be744f393f28ee45bf111d23bee2261d3f00cc2bbf350b24be744f393f3acd13bf8c6558be9ee6493f9ee649bf8c6558be3acd133f28ee45bf111d23bee2261d3f00cc2bbf350b24be744f393f9ee649bf8c6558be3acd133f3acd13bf8c6558be9ee6493f8d7dfebeb8fa9fbe233c4f3fbb321dbf3fc1a4beeb7e383fbb321dbf3fc1a4beeb7e383f8d7dfebeb8fa9fbe233c4f3fec05d1beec05d1beec05513fbb321dbf3fc1a4beeb7e383fec05d1beec05d1beec05513ff7bc23bf4a51dabef7bc233ff7bc23bf4a51dabef7bc233feb7e38bf3fc1a4bebb321d3fbb321dbf3fc1a4beeb7e383fbb321dbf3fc1a4beeb7e383feb7e38bf3fc1a4bebb321d3f9ee649bf8c6558be3acd133fbb321dbf3fc1a4beeb7e383f9ee649bf8c6558be3acd133f3acd13bf8c6558be9ee6493f9ee649bf8c6558be3acd133feb7e38bf3fc1a4bebb321d3f233c4fbfb8fa9fbe8d7dfe3eeb7e38bf3fc1a4bebb321d3ff7bc23bf4a51dabef7bc233fec0551bfec05d1beec05d13e233c4fbfb8fa9fbe8d7dfe3eeb7e38bf3fc1a4bebb321d3fec0551bfec05d1beec05d13eec05d1beec05d1beec05513f8c6558be3acd13bf9ee6493f4a51dabef7bc23bff7bc233f8c6558be3acd13bf9ee6493f00000000f30435bff304353f8c6558be9ee649bf3acd133f8c6558be9ee649bf3acd133f4a51dabef7bc23bff7bc233f8c6558be3acd13bf9ee6493fec05d1beec0551bfec05d13e4a51dabef7bc23bff7bc233f8c6558be9ee649bf3acd133fec05d1beec0551bfec05d13ef7bc23bff7bc23bf4a51da3e4a51dabef7bc23bff7bc233ff7bc23bff7bc23bf4a51da3eec0551bfec05d1beec05d13ef7bc23bf4a51dabef7bc233ff7bc23bf4a51dabef7bc233f4a51dabef7bc23bff7bc233ff7bc23bff7bc23bf4a51da3eec05d1beec05d1beec05513f4a51dabef7bc23bff7bc233ff7bc23bf4a51dabef7bc233ff30435bff30435bf000000009de649bf39cd13bf8b65583e39cd13bf9de649bf8b65583e9de649bf39cd13bf8b65583eec0551bfec05d1beec05d13ef7bc23bff7bc23bf4a51da3ef7bc23bff7bc23bf4a51da3e39cd13bf9de649bf8b65583e9de649bf39cd13bf8b65583eec05d1beec0551bfec05d13e39cd13bf9de649bf8b65583ef7bc23bff7bc23bf4a51da3e00000000c2c547bebf147b3f4b2f4abd0bac16bee9e57c3f00000000834016beac3a7d3f4b2f4abd0bac16bee9e57c3f6fb3c9bd6fb3c9bd37817d3f0000000035bdc8bd6dc47e3f00000000834016beac3a7d3f4b2f4abd0bac16bee9e57c3f0000000035bdc8bd6dc47e3f6fb3c9bd6fb3c9bd37817d3f0bac16be4b2f4abde9e57c3fb7314abdb7314abd1b607f3fb7314abdb7314abd1b607f3f0bac16be4b2f4abde9e57c3fc2c547be00000000bf147b3fb7314abdb7314abd1b607f3fc2c547be00000000bf147b3f00000000000000000000803f00000000000000000000803f0000000030fb48bd10b17f3fb7314abdb7314abd1b607f3fb7314abdb7314abd1b607f3f0000000030fb48bd10b17f3f0000000035bdc8bd6dc47e3fb7314abdb7314abd1b607f3f0000000035bdc8bd6dc47e3f6fb3c9bd6fb3c9bd37817d3f0000000015efc3be5e836c3fc873cdbdb94196be6c60733f0000000032a094be0bfa743fc873cdbdb94196be6c60733feb864bbeeb864bbec9ad753f00000000c2c547bebf147b3f0000000032a094be0bfa743fc873cdbdb94196be6c60733f00000000c2c547bebf147b3fc2c547be00000000bf147b3f0bac16be4b2f4abde9e57c3f927849bed7ce4cbd69ab7a3f0bac16be4b2f4abde9e57c3f6fb3c9bd6fb3c9bd37817d3f28aa4abe288dccbd4ba1793f927849bed7ce4cbd69ab7a3f0bac16be4b2f4abde9e57c3f28aa4abe288dccbd4ba1793f6fb3c9bd6fb3c9bd37817d3f4b2f4abd0bac16bee9e57c3f339718be339718be3b407a3f339718be339718be3b407a3f4b2f4abd0bac16bee9e57c3f00000000c2c547bebf147b3f339718be339718be3b407a3f00000000c2c547bebf147b3feb864bbeeb864bbec9ad753feb864bbeeb864bbec9ad753fc1594bbed91719be10f7773f339718be339718be3b407a3f339718be339718be3b407a3fc1594bbed91719be10f7773f28aa4abe288dccbd4ba1793f339718be339718be3b407a3f28aa4abe288dccbd4ba1793f6fb3c9bd6fb3c9bd37817d3feb864bbeeb864bbec9ad753fdb9c7cbe917c19be451a753fc1594bbed91719be10f7773fdb9c7cbe917c19be451a753fb94196bec873cdbd6c60733f28aa4abe288dccbd4ba1793fc1594bbed91719be10f7773fdb9c7cbe917c19be451a753f28aa4abe288dccbd4ba1793fb94196bec873cdbd6c60733f9480adbe6fef4dbd4e82703f85ca7abe0efc4dbd92de773f85ca7abe0efc4dbd92de773f9480adbe6fef4dbd4e82703f15efc3be000000005e836c3f85ca7abe0efc4dbd92de773f15efc3be000000005e836c3fc2c547be00000000bf147b3fc2c547be00000000bf147b3f927849bed7ce4cbd69ab7a3f85ca7abe0efc4dbd92de773f85ca7abe0efc4dbd92de773f927849bed7ce4cbd69ab7a3f28aa4abe288dccbd4ba1793f85ca7abe0efc4dbd92de773f28aa4abe288dccbd4ba1793fb94196bec873cdbd6c60733f0000000015efc3be5e836c3f00000000da390ebf31db543fe3db55beb429cfbe04ec633f00000000da390ebf31db543f00000000f30435bff304353f8c6558be3acd13bf9ee6493f8c6558be3acd13bf9ee6493fe3db55beb429cfbe04ec633f00000000da390ebf31db543fec05d1beec05d1beec05513fe3db55beb429cfbe04ec633f8c6558be3acd13bf9ee6493fb429cfbee3db55be04ec633fa8929ebea8929ebe3b23663f8947d1be82e09ebed7b75b3fa8929ebea8929ebe3b23663fe3db55beb429cfbe04ec633fec05d1beec05d1beec05513f8947d1be82e09ebed7b75b3fa8929ebea8929ebe3b23663fec05d1beec05d1beec05513f15efc3be000000005e836c3f9480adbe6fef4dbd4e82703f4a99c7be3a6257bd745c6b3f9480adbe6fef4dbd4e82703fb94196bec873cdbd6c60733f90b2cabe0714d7bda18a693f4a99c7be3a6257bd745c6b3f9480adbe6fef4dbd4e82703f90b2cabe0714d7bda18a693fb94196bec873cdbd6c60733fdb9c7cbe917c19be451a753f6255b3be52d81ebec4786c3f6255b3be52d81ebec4786c3fdb9c7cbe917c19be451a753feb864bbeeb864bbec9ad753f6255b3be52d81ebec4786c3feb864bbeeb864bbec9ad753fb429cfbee3db55be04ec633fb429cfbee3db55be04ec633fa538cdbe61ed20be370f673f6255b3be52d81ebec4786c3f6255b3be52d81ebec4786c3fa538cdbe61ed20be370f673f90b2cabe0714d7bda18a693f6255b3be52d81ebec4786c3f90b2cabe0714d7bda18a693fb94196bec873cdbd6c60733fe3db55beb429cfbe04ec633fa8929ebea8929ebe3b23663f0cf051be6d629bbe76366e3fa8929ebea8929ebe3b23663fb429cfbee3db55be04ec633feb864bbeeb864bbec9ad753f0cf051be6d629bbe76366e3fa8929ebea8929ebe3b23663feb864bbeeb864bbec9ad753feb864bbeeb864bbec9ad753fc873cdbdb94196be6c60733f0cf051be6d629bbe76366e3fc873cdbdb94196be6c60733f0000000015efc3be5e836c3fe3db55beb429cfbe04ec633f0cf051be6d629bbe76366e3fc873cdbdb94196be6c60733fe3db55beb429cfbe04ec633f3acd13bf8c6558be9ee6493fe2261dbf111d23be28ee453fc50d13bf99db22be7d8e4d3fe2261dbf111d23be28ee453f46d425bf9243dabdb91c413f69df11bfa6add9bd569b503fc50d13bf99db22be7d8e4d3fe2261dbf111d23be28ee453f69df11bfa6add9bd569b503f46d425bf9243dabdb91c413fdecb2dbf76bb5abd97773b3fe2861abfb2f85abd67a44b3fe2861abfb2f85abd67a44b3fdecb2dbf76bb5abd97773b3ff30435bf00000000f304353fe2861abfb2f85abd67a44b3ff30435bf00000000f304353fda390ebf0000000031db543fda390ebf0000000031db543f084310bfdaff59bdd80a533fe2861abfb2f85abd67a44b3fe2861abfb2f85abd67a44b3f084310bfdaff59bdd80a533f69df11bfa6add9bd569b503fe2861abfb2f85abd67a44b3f69df11bfa6add9bd569b503f46d425bf9243dabdb91c413fb429cfbee3db55be04ec633fd212e4bea63821be42a0613fa538cdbe61ed20be370f673fd212e4bea63821be42a0613f94fbf7be62c0d7bde4565e3f90b2cabe0714d7bda18a693fa538cdbe61ed20be370f673fd212e4bea63821be42a0613f90b2cabe0714d7bda18a693f94fbf7be62c0d7bde4565e3fcc6605bfe83958bd9b135a3f22a4debe4d6a58bdd121663f22a4debe4d6a58bdd121663fcc6605bfe83958bd9b135a3fda390ebf0000000031db543f22a4debe4d6a58bdd121663fda390ebf0000000031db543f15efc3be000000005e836c3f15efc3be000000005e836c3f4a99c7be3a6257bd745c6b3f22a4debe4d6a58bdd121663f22a4debe4d6a58bdd121663f4a99c7be3a6257bd745c6b3f90b2cabe0714d7bda18a693f22a4debe4d6a58bdd121663f90b2cabe0714d7bda18a693f94fbf7be62c0d7bde4565e3fda390ebf0000000031db543fcc6605bfe83958bd9b135a3f084310bfdaff59bdd80a533fcc6605bfe83958bd9b135a3f94fbf7be62c0d7bde4565e3f69df11bfa6add9bd569b503f084310bfdaff59bdd80a533fcc6605bfe83958bd9b135a3f69df11bfa6add9bd569b503f94fbf7be62c0d7bde4565e3fd212e4bea63821be42a0613f415808bfc5ab22bedcd1543f415808bfc5ab22bedcd1543fd212e4bea63821be42a0613fb429cfbee3db55be04ec633f415808bfc5ab22bedcd1543fb429cfbee3db55be04ec633f3acd13bf8c6558be9ee6493f3acd13bf8c6558be9ee6493fc50d13bf99db22be7d8e4d3f415808bfc5ab22bedcd1543f415808bfc5ab22bedcd1543fc50d13bf99db22be7d8e4d3f69df11bfa6add9bd569b503f415808bfc5ab22bedcd1543f69df11bfa6add9bd569b503f94fbf7be62c0d7bde4565e3fec05d1beec05d1beec05513f8d7dfebeb8fa9fbe233c4f3f8947d1be82e09ebed7b75b3f8d7dfebeb8fa9fbe233c4f3f3acd13bf8c6558be9ee6493fb429cfbee3db55be04ec633f8947d1be82e09ebed7b75b3f8d7dfebeb8fa9fbe233c4f3fb429cfbee3db55be04ec633f00000000000080bf0000000000000000bf147bbfc2c547bec2c547bebf147bbf0000000000000000bf147bbfc2c547be000000005e836cbf15efc3beea864bbec8ad75bfea864bbeea864bbec8ad75bfea864bbec2c547bebf147bbf0000000000000000bf147bbfc2c547be15efc3be5e836cbf00000000c2c547bebf147bbf00000000ea864bbec8ad75bfea864bbe000000005e836cbf15efc3be0000000031db54bfda390ebfe3db55be04ec63bfb429cfbe0000000031db54bfda390ebf00000000f30435bff30435bf8c6558be9ee649bf3acd13bf8c6558be9ee649bf3acd13bfe3db55be04ec63bfb429cfbe0000000031db54bfda390ebfec05d1beec0551bfec05d1bee3db55be04ec63bfb429cfbe8c6558be9ee649bf3acd13bfec05d1beec0551bfec05d1beb429cfbe04ec63bfe3db55bee3db55be04ec63bfb429cfbeb429cfbe04ec63bfe3db55be15efc3be5e836cbf00000000ea864bbec8ad75bfea864bbeea864bbec8ad75bfea864bbee3db55be04ec63bfb429cfbeb429cfbe04ec63bfe3db55be000000005e836cbf15efc3bee3db55be04ec63bfb429cfbeea864bbec8ad75bfea864bbef30435bff30435bf00000000da390ebf31db54bf0000000039cd13bf9de649bf8b6558beda390ebf31db54bf0000000015efc3be5e836cbf00000000b429cfbe04ec63bfe3db55beb429cfbe04ec63bfe3db55be39cd13bf9de649bf8b6558beda390ebf31db54bf00000000ec05d1beec0551bfec05d1be39cd13bf9de649bf8b6558beb429cfbe04ec63bfe3db55be00000000f30435bff30435bf00000000da390ebf31db54bf8c6558be3acd13bf9ee649bf00000000da390ebf31db54bf0000000015efc3be5e836cbfe3db55beb429cfbe04ec63bfe3db55beb429cfbe04ec63bf8c6558be3acd13bf9ee649bf00000000da390ebf31db54bfec05d1beec05d1beec0551bf8c6558be3acd13bf9ee649bfe3db55beb429cfbe04ec63bf0000000015efc3be5e836cbf0000000032a094be0bfa74bfc873cdbdb94196be6c6073bfc873cdbdb94196be6c6073bf0000000032a094be0bfa74bf00000000c2c547bebf147bbfc873cdbdb94196be6c6073bf00000000c2c547bebf147bbfeb864bbeeb864bbec9ad75bf00000000c2c547bebf147bbf00000000834016beac3a7dbf4b2f4abd0bac16bee9e57cbf4b2f4abd0bac16bee9e57cbf00000000834016beac3a7dbf0000000035bdc8bd6dc47ebf4b2f4abd0bac16bee9e57cbf0000000035bdc8bd6dc47ebf6fb3c9bd6fb3c9bd37817dbf0000000000000000000080bfb7314abdb7314abd1b607fbf0000000030fb48bd10b17fbfb7314abdb7314abd1b607fbf6fb3c9bd6fb3c9bd37817dbf0000000035bdc8bd6dc47ebf0000000030fb48bd10b17fbfb7314abdb7314abd1b607fbf0000000035bdc8bd6dc47ebf6fb3c9bd6fb3c9bd37817dbfb7314abdb7314abd1b607fbf0bac16be4b2f4abde9e57cbfb7314abdb7314abd1b607fbf0000000000000000000080bfc2c547be00000000bf147bbf0bac16be4b2f4abde9e57cbfb7314abdb7314abd1b607fbfc2c547be00000000bf147bbfc2c547be00000000bf147bbf927849bed7ce4cbd69ab7abf0bac16be4b2f4abde9e57cbf0bac16be4b2f4abde9e57cbf927849bed7ce4cbd69ab7abf28aa4abe288dccbd4ba179bf0bac16be4b2f4abde9e57cbf28aa4abe288dccbd4ba179bf6fb3c9bd6fb3c9bd37817dbfeb864bbeeb864bbec9ad75bf339718be339718be3b407abfc1594bbed91719be10f777bf339718be339718be3b407abf6fb3c9bd6fb3c9bd37817dbf28aa4abe288dccbd4ba179bfc1594bbed91719be10f777bf339718be339718be3b407abf28aa4abe288dccbd4ba179bf6fb3c9bd6fb3c9bd37817dbf339718be339718be3b407abf4b2f4abd0bac16bee9e57cbf339718be339718be3b407abfeb864bbeeb864bbec9ad75bf00000000c2c547bebf147bbf4b2f4abd0bac16bee9e57cbf339718be339718be3b407abf00000000c2c547bebf147bbfeb864bbeeb864bbec9ad75bfc1594bbed91719be10f777bfdb9c7cbe917c19be451a75bfdb9c7cbe917c19be451a75bfc1594bbed91719be10f777bf28aa4abe288dccbd4ba179bfdb9c7cbe917c19be451a75bf28aa4abe288dccbd4ba179bfb94196bec873cdbd6c6073bfc2c547be00000000bf147bbf85ca7abe0efc4dbd92de77bf927849bed7ce4cbd69ab7abf85ca7abe0efc4dbd92de77bfb94196bec873cdbd6c6073bf28aa4abe288dccbd4ba179bf927849bed7ce4cbd69ab7abf85ca7abe0efc4dbd92de77bf28aa4abe288dccbd4ba179bfb94196bec873cdbd6c6073bf85ca7abe0efc4dbd92de77bf9480adbe6fef4dbd4e8270bf85ca7abe0efc4dbd92de77bfc2c547be00000000bf147bbf15efc3be000000005e836cbf9480adbe6fef4dbd4e8270bf85ca7abe0efc4dbd92de77bf15efc3be000000005e836cbf15efc3be000000005e836cbf4a99c7be3a6257bd745c6bbf9480adbe6fef4dbd4e8270bf9480adbe6fef4dbd4e8270bf4a99c7be3a6257bd745c6bbf90b2cabe0714d7bda18a69bf9480adbe6fef4dbd4e8270bf90b2cabe0714d7bda18a69bfb94196bec873cdbd6c6073bfb429cfbee3db55be04ec63bf6255b3be52d81ebec4786cbfa538cdbe61ed20be370f67bf6255b3be52d81ebec4786cbfb94196bec873cdbd6c6073bf90b2cabe0714d7bda18a69bfa538cdbe61ed20be370f67bf6255b3be52d81ebec4786cbf90b2cabe0714d7bda18a69bfb94196bec873cdbd6c6073bf6255b3be52d81ebec4786cbfdb9c7cbe917c19be451a75bf6255b3be52d81ebec4786cbfb429cfbee3db55be04ec63bfeb864bbeeb864bbec9ad75bfdb9c7cbe917c19be451a75bf6255b3be52d81ebec4786cbfeb864bbeeb864bbec9ad75bfb429cfbee3db55be04ec63bf8947d1be82e09ebed7b75bbfa8929ebea8929ebe3b2366bfa8929ebea8929ebe3b2366bf8947d1be82e09ebed7b75bbfec05d1beec05d1beec0551bfa8929ebea8929ebe3b2366bfec05d1beec05d1beec0551bfe3db55beb429cfbe04ec63bfe3db55beb429cfbe04ec63bf0cf051be6d629bbe76366ebfa8929ebea8929ebe3b2366bfa8929ebea8929ebe3b2366bf0cf051be6d629bbe76366ebfeb864bbeeb864bbec9ad75bfa8929ebea8929ebe3b2366bfeb864bbeeb864bbec9ad75bfb429cfbee3db55be04ec63bfeb864bbeeb864bbec9ad75bf0cf051be6d629bbe76366ebfc873cdbdb94196be6c6073bfc873cdbdb94196be6c6073bf0cf051be6d629bbe76366ebfe3db55beb429cfbe04ec63bfc873cdbdb94196be6c6073bfe3db55beb429cfbe04ec63bf0000000015efc3be5e836cbf3acd13bf8c6558be9ee649bfc50d13bf99db22be7d8e4dbfe2261dbf111d23be28ee45bfe2261dbf111d23be28ee45bfc50d13bf99db22be7d8e4dbf69df11bfa6add9bd569b50bfe2261dbf111d23be28ee45bf69df11bfa6add9bd569b50bf46d425bf9243dabdb91c41bfda390ebf0000000031db54bfe2861abfb2f85abd67a44bbf084310bfdaff59bdd80a53bfe2861abfb2f85abd67a44bbf46d425bf9243dabdb91c41bf69df11bfa6add9bd569b50bf084310bfdaff59bdd80a53bfe2861abfb2f85abd67a44bbf69df11bfa6add9bd569b50bf46d425bf9243dabdb91c41bfe2861abfb2f85abd67a44bbfdecb2dbf76bb5abd97773bbfe2861abfb2f85abd67a44bbfda390ebf0000000031db54bff30435bf00000000f30435bfdecb2dbf76bb5abd97773bbfe2861abfb2f85abd67a44bbff30435bf00000000f30435bfec05d1beec05d1beec0551bf8947d1be82e09ebed7b75bbf8d7dfebeb8fa9fbe233c4fbf8d7dfebeb8fa9fbe233c4fbf8947d1be82e09ebed7b75bbfb429cfbee3db55be04ec63bf8d7dfebeb8fa9fbe233c4fbfb429cfbee3db55be04ec63bf3acd13bf8c6558be9ee649bfda390ebf0000000031db54bf084310bfdaff59bdd80a53bfcc6605bfe83958bd9b135abfcc6605bfe83958bd9b135abf084310bfdaff59bdd80a53bf69df11bfa6add9bd569b50bfcc6605bfe83958bd9b135abf69df11bfa6add9bd569b50bf94fbf7be62c0d7bde4565ebf3acd13bf8c6558be9ee649bf415808bfc5ab22bedcd154bfc50d13bf99db22be7d8e4dbf415808bfc5ab22bedcd154bf94fbf7be62c0d7bde4565ebf69df11bfa6add9bd569b50bfc50d13bf99db22be7d8e4dbf415808bfc5ab22bedcd154bf69df11bfa6add9bd569b50bf94fbf7be62c0d7bde4565ebf415808bfc5ab22bedcd154bfd212e4bea63821be42a061bf415808bfc5ab22bedcd154bf3acd13bf8c6558be9ee649bfb429cfbee3db55be04ec63bfd212e4bea63821be42a061bf415808bfc5ab22bedcd154bfb429cfbee3db55be04ec63bfb429cfbee3db55be04ec63bfa538cdbe61ed20be370f67bfd212e4bea63821be42a061bfd212e4bea63821be42a061bfa538cdbe61ed20be370f67bf90b2cabe0714d7bda18a69bfd212e4bea63821be42a061bf90b2cabe0714d7bda18a69bf94fbf7be62c0d7bde4565ebf15efc3be000000005e836cbf22a4debe4d6a58bdd12166bf4a99c7be3a6257bd745c6bbf22a4debe4d6a58bdd12166bf94fbf7be62c0d7bde4565ebf90b2cabe0714d7bda18a69bf4a99c7be3a6257bd745c6bbf22a4debe4d6a58bdd12166bf90b2cabe0714d7bda18a69bf94fbf7be62c0d7bde4565ebf22a4debe4d6a58bdd12166bfcc6605bfe83958bd9b135abf22a4debe4d6a58bdd12166bf15efc3be000000005e836cbfda390ebf0000000031db54bfcc6605bfe83958bd9b135abf22a4debe4d6a58bdd12166bfda390ebf0000000031db54bff30435bf00000000f30435bf97773bbf76bb5abddecb2dbfdecb2dbf76bb5abd97773bbf97773bbf76bb5abddecb2dbfb91c41bf9243dabd46d425bf46d425bf9243dabdb91c41bfdecb2dbf76bb5abd97773bbf97773bbf76bb5abddecb2dbf46d425bf9243dabdb91c41bf9ee649bf8c6558be3acd13bf744f39bf350b24be00cc2bbf28ee45bf111d23bee2261dbf744f39bf350b24be00cc2bbf46d425bf9243dabdb91c41bfb91c41bf9243dabd46d425bf28ee45bf111d23bee2261dbf744f39bf350b24be00cc2bbfb91c41bf9243dabd46d425bf46d425bf9243dabdb91c41bf744f39bf350b24be00cc2bbfe2261dbf111d23be28ee45bf744f39bf350b24be00cc2bbf9ee649bf8c6558be3acd13bf3acd13bf8c6558be9ee649bfe2261dbf111d23be28ee45bf744f39bf350b24be00cc2bbf3acd13bf8c6558be9ee649bf9ee649bf8c6558be3acd13bf233c4fbfb8fa9fbe8d7dfebeeb7e38bf3fc1a4bebb321dbfeb7e38bf3fc1a4bebb321dbf233c4fbfb8fa9fbe8d7dfebeec0551bfec05d1beec05d1beeb7e38bf3fc1a4bebb321dbfec0551bfec05d1beec05d1bef7bc23bf4a51dabef7bc23bff7bc23bf4a51dabef7bc23bfbb321dbf3fc1a4beeb7e38bfeb7e38bf3fc1a4bebb321dbfeb7e38bf3fc1a4bebb321dbfbb321dbf3fc1a4beeb7e38bf3acd13bf8c6558be9ee649bfeb7e38bf3fc1a4bebb321dbf3acd13bf8c6558be9ee649bf9ee649bf8c6558be3acd13bf3acd13bf8c6558be9ee649bfbb321dbf3fc1a4beeb7e38bf8d7dfebeb8fa9fbe233c4fbfbb321dbf3fc1a4beeb7e38bff7bc23bf4a51dabef7bc23bfec05d1beec05d1beec0551bf8d7dfebeb8fa9fbe233c4fbfbb321dbf3fc1a4beeb7e38bfec05d1beec05d1beec0551bfec0551bfec05d1beec05d1be9de649bf39cd13bf8b6558bef7bc23bff7bc23bf4a51dabe9de649bf39cd13bf8b6558bef30435bff30435bf0000000039cd13bf9de649bf8b6558be39cd13bf9de649bf8b6558bef7bc23bff7bc23bf4a51dabe9de649bf39cd13bf8b6558beec05d1beec0551bfec05d1bef7bc23bff7bc23bf4a51dabe39cd13bf9de649bf8b6558beec05d1beec0551bfec05d1be4a51dabef7bc23bff7bc23bff7bc23bff7bc23bf4a51dabe4a51dabef7bc23bff7bc23bfec05d1beec05d1beec0551bff7bc23bf4a51dabef7bc23bff7bc23bf4a51dabef7bc23bff7bc23bff7bc23bf4a51dabe4a51dabef7bc23bff7bc23bfec0551bfec05d1beec05d1bef7bc23bff7bc23bf4a51dabef7bc23bf4a51dabef7bc23bf00000000f30435bff30435bf8c6558be3acd13bf9ee649bf8c6558be9ee649bf3acd13bf8c6558be3acd13bf9ee649bfec05d1beec05d1beec0551bf4a51dabef7bc23bff7bc23bf4a51dabef7bc23bff7bc23bf8c6558be9ee649bf3acd13bf8c6558be3acd13bf9ee649bfec05d1beec0551bfec05d1be8c6558be9ee649bf3acd13bf4a51dabef7bc23bff7bc23bfbf147bbfc2c547be00000000e9e57cbf0bac16be4b2f4abdac3a7dbf834016be00000000e9e57cbf0bac16be4b2f4abd37817dbf6fb3c9bd6fb3c9bd6dc47ebf35bdc8bd00000000ac3a7dbf834016be00000000e9e57cbf0bac16be4b2f4abd6dc47ebf35bdc8bd0000000037817dbf6fb3c9bd6fb3c9bde9e57cbf4b2f4abd0bac16be1c607fbfb8314abdb8314abd1c607fbfb8314abdb8314abde9e57cbf4b2f4abd0bac16bebf147bbf00000000c2c547be1c607fbfb8314abdb8314abdbf147bbf00000000c2c547be000080bf0000000000000000000080bf000000000000000010b17fbf30fb48bd000000001c607fbfb8314abdb8314abd1c607fbfb8314abdb8314abd10b17fbf30fb48bd000000006dc47ebf35bdc8bd000000001c607fbfb8314abdb8314abd6dc47ebf35bdc8bd0000000037817dbf6fb3c9bd6fb3c9bd5e836cbf15efc3be000000006c6073bfba4196bec873cdbd0bfa74bf32a094be000000006c6073bfba4196bec873cdbdc8ad75bfea864bbeea864bbebf147bbfc2c547be000000000bfa74bf32a094be000000006c6073bfba4196bec873cdbdbf147bbfc2c547be00000000bf147bbf00000000c2c547bee9e57cbf4b2f4abd0bac16be69ab7abfd6ce4cbd927849bee9e57cbf4b2f4abd0bac16be37817dbf6fb3c9bd6fb3c9bd4ba179bf278dccbd28aa4abe69ab7abfd6ce4cbd927849bee9e57cbf4b2f4abd0bac16be4ba179bf278dccbd28aa4abe37817dbf6fb3c9bd6fb3c9bde9e57cbf0bac16be4b2f4abd3b407abf339718be339718be3b407abf339718be339718bee9e57cbf0bac16be4b2f4abdbf147bbfc2c547be000000003b407abf339718be339718bebf147bbfc2c547be00000000c8ad75bfea864bbeea864bbec8ad75bfea864bbeea864bbe10f777bfd81719bec0594bbe3b407abf339718be339718be3b407abf339718be339718be10f777bfd81719bec0594bbe4ba179bf278dccbd28aa4abe3b407abf339718be339718be4ba179bf278dccbd28aa4abe37817dbf6fb3c9bd6fb3c9bdc8ad75bfea864bbeea864bbe451a75bf907c19bedc9c7cbe10f777bfd81719bec0594bbe451a75bf907c19bedc9c7cbe6c6073bfc873cdbdba4196be4ba179bf278dccbd28aa4abe10f777bfd81719bec0594bbe451a75bf907c19bedc9c7cbe4ba179bf278dccbd28aa4abe6c6073bfc873cdbdba4196be4e8270bf6fef4dbd9580adbe92de77bf0efc4dbd86ca7abe92de77bf0efc4dbd86ca7abe4e8270bf6fef4dbd9580adbe5e836cbf0000000015efc3be92de77bf0efc4dbd86ca7abe5e836cbf0000000015efc3bebf147bbf00000000c2c547bebf147bbf00000000c2c547be69ab7abfd6ce4cbd927849be92de77bf0efc4dbd86ca7abe92de77bf0efc4dbd86ca7abe69ab7abfd6ce4cbd927849be4ba179bf278dccbd28aa4abe92de77bf0efc4dbd86ca7abe4ba179bf278dccbd28aa4abe6c6073bfc873cdbdba4196be5e836cbf15efc3be0000000031db54bfda390ebf0000000004ec63bfb429cfbee3db55be31db54bfda390ebf00000000f30435bff30435bf000000009de649bf39cd13bf8b6558be9de649bf39cd13bf8b6558be04ec63bfb429cfbee3db55be31db54bfda390ebf00000000ec0551bfec05d1beec05d1be04ec63bfb429cfbee3db55be9de649bf39cd13bf8b6558be04ec63bfe3db55beb429cfbe3b2366bfa8929ebea8929ebed7b75bbf82e09ebe8947d1be3b2366bfa8929ebea8929ebe04ec63bfb429cfbee3db55beec0551bfec05d1beec05d1bed7b75bbf82e09ebe8947d1be3b2366bfa8929ebea8929ebeec0551bfec05d1beec05d1be5e836cbf0000000015efc3be4e8270bf6fef4dbd9580adbe745c6bbf3a6257bd4a99c7be4e8270bf6fef4dbd9580adbe6c6073bfc873cdbdba4196bea18a69bf0714d7bd90b2cabe745c6bbf3a6257bd4a99c7be4e8270bf6fef4dbd9580adbea18a69bf0714d7bd90b2cabe6c6073bfc873cdbdba4196be451a75bf907c19bedc9c7cbec4786cbf52d81ebe6355b3bec4786cbf52d81ebe6355b3be451a75bf907c19bedc9c7cbec8ad75bfea864bbeea864bbec4786cbf52d81ebe6355b3bec8ad75bfea864bbeea864bbe04ec63bfe3db55beb429cfbe04ec63bfe3db55beb429cfbe370f67bf61ed20bea538cdbec4786cbf52d81ebe6355b3bec4786cbf52d81ebe6355b3be370f67bf61ed20bea538cdbea18a69bf0714d7bd90b2cabec4786cbf52d81ebe6355b3bea18a69bf0714d7bd90b2cabe6c6073bfc873cdbdba4196be04ec63bfb429cfbee3db55be3b2366bfa8929ebea8929ebe76366ebf6c629bbe0bf051be3b2366bfa8929ebea8929ebe04ec63bfe3db55beb429cfbec8ad75bfea864bbeea864bbe76366ebf6c629bbe0bf051be3b2366bfa8929ebea8929ebec8ad75bfea864bbeea864bbec8ad75bfea864bbeea864bbe6c6073bfba4196bec873cdbd76366ebf6c629bbe0bf051be6c6073bfba4196bec873cdbd5e836cbf15efc3be0000000004ec63bfb429cfbee3db55be76366ebf6c629bbe0bf051be6c6073bfba4196bec873cdbd04ec63bfb429cfbee3db55be9ee649bf8c6558be3acd13bf28ee45bf111d23bee2261dbf7d8e4dbf9adb22bec60d13bf28ee45bf111d23bee2261dbfb91c41bf9243dabd46d425bf569b50bfa6add9bd69df11bf7d8e4dbf9adb22bec60d13bf28ee45bf111d23bee2261dbf569b50bfa6add9bd69df11bfb91c41bf9243dabd46d425bf97773bbf76bb5abddecb2dbf67a44bbfb2f85abde2861abf67a44bbfb2f85abde2861abf97773bbf76bb5abddecb2dbff30435bf00000000f30435bf67a44bbfb2f85abde2861abff30435bf00000000f30435bf31db54bf00000000da390ebf31db54bf00000000da390ebfd80a53bfdaff59bd084310bf67a44bbfb2f85abde2861abf67a44bbfb2f85abde2861abfd80a53bfdaff59bd084310bf569b50bfa6add9bd69df11bf67a44bbfb2f85abde2861abf569b50bfa6add9bd69df11bfb91c41bf9243dabd46d425bf04ec63bfe3db55beb429cfbe42a061bfa63821bed212e4be370f67bf61ed20bea538cdbe42a061bfa63821bed212e4bee4565ebf62c0d7bd94fbf7bea18a69bf0714d7bd90b2cabe370f67bf61ed20bea538cdbe42a061bfa63821bed212e4bea18a69bf0714d7bd90b2cabee4565ebf62c0d7bd94fbf7be9b135abfe83958bdcc6605bfd12166bf4d6a58bd22a4debed12166bf4d6a58bd22a4debe9b135abfe83958bdcc6605bf31db54bf00000000da390ebfd12166bf4d6a58bd22a4debe31db54bf00000000da390ebf5e836cbf0000000015efc3be5e836cbf0000000015efc3be745c6bbf3a6257bd4a99c7bed12166bf4d6a58bd22a4debed12166bf4d6a58bd22a4debe745c6bbf3a6257bd4a99c7bea18a69bf0714d7bd90b2cabed12166bf4d6a58bd22a4debea18a69bf0714d7bd90b2cabee4565ebf62c0d7bd94fbf7be31db54bf00000000da390ebf9b135abfe83958bdcc6605bfd80a53bfdaff59bd084310bf9b135abfe83958bdcc6605bfe4565ebf62c0d7bd94fbf7be569b50bfa6add9bd69df11bfd80a53bfdaff59bd084310bf9b135abfe83958bdcc6605bf569b50bfa6add9bd69df11bfe4565ebf62c0d7bd94fbf7be42a061bfa63821bed212e4bedcd154bfc5ab22be415808bfdcd154bfc5ab22be415808bf42a061bfa63821bed212e4be04ec63bfe3db55beb429cfbedcd154bfc5ab22be415808bf04ec63bfe3db55beb429cfbe9ee649bf8c6558be3acd13bf9ee649bf8c6558be3acd13bf7d8e4dbf9adb22bec60d13bfdcd154bfc5ab22be415808bfdcd154bfc5ab22be415808bf7d8e4dbf9adb22bec60d13bf569b50bfa6add9bd69df11bfdcd154bfc5ab22be415808bf569b50bfa6add9bd69df11bfe4565ebf62c0d7bd94fbf7beec0551bfec05d1beec05d1be233c4fbfb8fa9fbe8d7dfebed7b75bbf82e09ebe8947d1be233c4fbfb8fa9fbe8d7dfebe9ee649bf8c6558be3acd13bf04ec63bfe3db55beb429cfbed7b75bbf82e09ebe8947d1be233c4fbfb8fa9fbe8d7dfebe04ec63bfe3db55beb429cfbe + m_CompressedMesh: + m_Vertices: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_UV: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Normals: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Tangents: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_Weights: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_NormalSigns: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_TangentSigns: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_FloatColors: + m_NumItems: 0 + m_Range: 0 + m_Start: 0 + m_Data: + m_BitSize: 0 + m_BoneIndices: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_Triangles: + m_NumItems: 0 + m_Data: + m_BitSize: 0 + m_UVInfo: 0 + m_LocalAABB: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 1, y: 1, z: 1} + m_MeshUsageFlags: 0 + m_CookingOptions: 30 + m_BakedConvexCollisionMesh: + m_BakedTriangleCollisionMesh: + m_MeshMetrics[0]: 1 + m_MeshMetrics[1]: 1 + m_MeshOptimizationFlags: 1 + m_StreamData: + serializedVersion: 2 + offset: 0 + size: 0 + path: diff --git a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters/TestCaseFilters.asset.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh.meta similarity index 64% rename from Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters/TestCaseFilters.asset.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh.meta index e021d76549d..13db6b57e10 100644 --- a/Tests/SRPTests/Projects/BuiltInGraphicsTest_Foundation/Assets/Test/TestFilters/TestCaseFilters.asset.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Meshes/SkyboxMesh.mesh.meta @@ -1,8 +1,8 @@ fileFormatVersion: 2 -guid: c5f675aa74d26584ca7f5db2da8a25af +guid: 0529e6c5f6dea8c4a8c2835ed7de57cb NativeFormatImporter: externalObjects: {} - mainObjectFileID: 0 + mainObjectFileID: 4300000 userData: assetBundleName: assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs new file mode 100644 index 00000000000..7a8a1ef3e3c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs @@ -0,0 +1,555 @@ +#if ENABLE_PATH_TRACING_SRP +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering.Denoising; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.Rendering.LiveGI +{ + using InstanceHandle = Handle; + using LightHandle = Handle; + using MaterialHandle = Handle; + + [Serializable] + internal class PathTracingSettings + { + [Header("Light Transport")] + [Range(0, 10)] + public int bounceCount = 2; + + [Range(1, 16)] + public int sampleCount = 1; + + [Range(1, 8)] + public int lightEvaluations = 1; + + [Min(0)] + public int maxIntensity; + + [HideInInspector] + public float exposureScale = 1.0f; + + // Keep emissive mesh sampling disabled for now, as we need a more reliable sceneMaterials.IsEmissive. + // If you change that, you need to modify the EMISSIVE_SAMPLING define in the shader + [HideInInspector] + public bool enableEmissiveSampling; + + // For now, some SRPs pre-multiply the light intensity with PI (like URP), while other don't + // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ + [HideInInspector] + public bool multiplyPunctualLightIntensityByPI; + + // This automatically adjust the light range based on the light intensity and range to better fit the falloff LUT (applies to punctual lights with inverse squared falloff only). + [HideInInspector] + public bool autoEstimateLUTRange = true; + + public LightPickingMethod lightPickingMethod = LightPickingMethod.Uniform; + + [HideInInspector] + public PathTermination pathTermination = PathTermination.RusianRoulette; + + [Header("Post Processing")] + public DenoiserType denoising = DenoiserType.None; + + [Header("Intersections")] + public RayTracingBackend raytracingBackend = RayTracingBackend.Hardware; + + // Use "only static" when previewing lightmaps. Use "All" to path trace the full frame + public RenderedGameObjectsFilter renderedGameObjects = RenderedGameObjectsFilter.OnlyStatic; + + [Header("Artistic Controls")] + public bool respectLightLayers = true; + + [Range(1, 10)] + public float albedoBoost = 1.0f; + + [Range(0, 10)] + public float indirectIntensity = 1.0f; + + [Range(0, 10)] + public float environmentIntensityMultiplier = 1.0f; + + [HideInInspector] + public Vector3Int reservoirGridSize = new Vector3Int(64, 16, 64); + + [HideInInspector] + public int reservoirsPerVoxel = 16; + + [Header("Debug")] + public bool showRayHeatmap = false; + } + + internal enum PathTracingOutput { FullPathTracer, GIPreview }; + + internal enum RayTracingBackend { Hardware = 0, Compute = 1 }; + + internal enum PathTermination { RusianRoulette = 0, NewUnbiased = 1, NewBiased = 2 }; + + internal class PathTracingContext : IDisposable + { + // Public API + #region Public API + + public PathTracingContext(PathTracingOutput pathTracingOutput) + { +#if UNITY_EDITOR + // Note: this code is not ready to run in the player, we just disable some parts to make sure the player builds successfully + _defaultMaterial = UnityEditor.AssetDatabase.GetBuiltinExtraResource("Default-Material.mat"); +#endif + _sceneUpdatesTracker = new SceneUpdatesTracker(); + + _pathTracingOutput = pathTracingOutput; + + _samplingResources = new SamplingResources(); +#if UNITY_EDITOR + _samplingResources.Load((uint)SamplingResources.ResourceType.All); +#endif + _emptyExposureTexture = RTHandles.Alloc(1, 1, + enableRandomWrite: true, name: "Empty EV100 Exposure"); + } + + public void Dispose() + { + _world?.Dispose(); + + _rayTracingContext?.Dispose(); + _rayTracingContext = null; + + _sceneUpdatesTracker?.Dispose(); + + _emptyExposureTexture?.Release(); + + _buildScratchBuffer?.Dispose(); + + _samplingResources?.Dispose(); + + // Delete temporary textures for the default material + CoreUtils.Destroy(_defaultMaterialDescriptor.Albedo); + CoreUtils.Destroy(_defaultMaterialDescriptor.Emission); + CoreUtils.Destroy(_defaultMaterialDescriptor.Transmission); + } + + internal RayTracingBackend SelectRayTracingBackend(RayTracingBackend requestedBackend, RayTracingResources resources) + { + if (!RayTracingContext.IsBackendSupported((UnifiedRayTracing.RayTracingBackend)requestedBackend)) + { + Debug.LogWarning("Hardware RayTracing not available. Falling back to Compute Shader based implementation."); + requestedBackend = RayTracingBackend.Compute; + } + + // Early exit if the backend has not changed and the RT resources are already loaded + if (_world != null && _world.GetAccelerationStructure() != null && _rayTracingShader != null && _currentRayTracingBackend == requestedBackend) + return requestedBackend; + + _rayTracingContext?.Dispose(); + + _rayTracingContext = new RayTracingContext((UnifiedRayTracing.RayTracingBackend)requestedBackend, resources); + _currentRayTracingBackend = requestedBackend; +#if UNITY_EDITOR + _rayTracingShader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader"); +#endif + _world?.Dispose(); + _world = new World(); + var worldResources = new WorldResourceSet(); +#if UNITY_EDITOR + worldResources.LoadFromAssetDatabase(); +#endif + _world.Init(_rayTracingContext, worldResources); + _defaultMaterialDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(_defaultMaterial); + var defaultHandle = _world.AddMaterial(in _defaultMaterialDescriptor, UVChannel.UV0); + _instanceIDToWorldMaterialHandles.Add(_defaultMaterial.GetEntityId(), defaultHandle); + _instanceIDToWorldMaterialDescriptors.Add(_defaultMaterial.GetEntityId(), _defaultMaterialDescriptor); + + return requestedBackend; + } + + // Handles scene updates. Should be called once per frame (and not per camera) + public void Update(CommandBuffer cmd, PathTracingSettings settings) + { + using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Update"))) + { + // TODO: enable this only for debugging + Unity.Collections.NativeLeakDetection.Mode = Unity.Collections.NativeLeakDetectionMode.EnabledWithStackTrace; + + var changes = _sceneUpdatesTracker.GetChanges(_pathTracingOutput == PathTracingOutput.GIPreview, false, false); + _sceneChanged = changes.HasChanges(); + + UpdateMaterials(_world, _instanceIDToWorldMaterialHandles, _instanceIDToWorldMaterialDescriptors, changes.addedMaterials, changes.removedMaterials, changes.changedMaterials); + UpdateInstances(_world, _instanceIDToWorldInstanceHandles, _instanceIDToWorldMaterialHandles, changes.addedInstances, changes.changedInstances, changes.removedInstances, settings.renderedGameObjects, settings.enableEmissiveSampling, _defaultMaterial); + UpdateLights(_world, _instanceIDToWorldLightHandles, changes.addedLights, changes.removedLights, changes.changedLights, settings); + + // Calculate scene bounds. + Bounds sceneBounds = new Bounds(); + if (settings.lightPickingMethod == LightPickingMethod.Regir || + settings.lightPickingMethod == LightPickingMethod.LightGrid) + { + var sceneRenderers = Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (Renderer r in sceneRenderers) + sceneBounds.Encapsulate(r.bounds); + } + + _world.SetEnvironmentMaterial(RenderSettings.skybox); + //_world.EnableEmissiveSampling = settings.enableEmissiveSampling; + _world.Build(sceneBounds, cmd, ref _buildScratchBuffer, _samplingResources, settings.enableEmissiveSampling); + + int newLightListHashCode = _world.LightListHashCode; + _sceneChanged |= (newLightListHashCode != _previousLightsHashCode); + _previousLightsHashCode = newLightListHashCode; + } + } + + public void Render(CommandBuffer cmd, Vector2Int scaledSize, Vector4 viewFustum, Matrix4x4 cameraToWorldMatrix, Matrix4x4 worldToCameraMatrix, Matrix4x4 projectionMatrix, Matrix4x4 previousViewProjection, PathTracingSettings settings, RTHandle output, RTHandle normals, RTHandle motionVectors, RTHandle debugOutput, int frameIndex, bool preExpose = false) + { + using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Render"))) + { + Debug.Assert(_world.GetAccelerationStructure() != null, "Acceleration structure does not exist. Did you call Update()?"); + + // Path-tracer Input + Util.BindWorld(cmd, _rayTracingShader, _world, _pathTracingOutput == PathTracingOutput.GIPreview ? 32 : 1024); + _rayTracingShader.SetVectorParam(cmd, Shader.PropertyToID("g_CameraFrustum"), viewFustum); + _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraToWorldMatrix"), cameraToWorldMatrix); + _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_PreviousViewProjection"), previousViewProjection); + + var viewProjection = projectionMatrix * worldToCameraMatrix; + _rayTracingShader.SetMatrixParam(cmd, Shader.PropertyToID("g_CameraViewProjection"), viewProjection); + + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_FrameIndex"), frameIndex); + + // For now the history rejection in the denoising is not compatible with stochastic jitter + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_EnableSubPixelJittering"), 0); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightPickingMethod"), (int)settings.lightPickingMethod); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_BounceCount"), settings.bounceCount); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_SampleCount"), settings.sampleCount); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_LightEvaluations"), settings.lightEvaluations); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PathtracerAsGiPreviewMode"), (_pathTracingOutput == PathTracingOutput.GIPreview) ? 1 : 0); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_CountNEERayAsPathSegment"), 1); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_RenderedInstances"), (int)settings.renderedGameObjects); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_PreExpose"), preExpose ? 1 : 0); + _rayTracingShader.SetIntParam(cmd, Shader.PropertyToID("g_MaxIntensity"), settings.maxIntensity > 0 ? settings.maxIntensity : int.MaxValue); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_ExposureScale"), settings.exposureScale); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_AlbedoBoost"), settings.albedoBoost); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_IndirectScale"), settings.indirectIntensity); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnvIntensityMultiplier"), settings.environmentIntensityMultiplier); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_EnableDebug"), settings.showRayHeatmap ? 1 : 0); + _rayTracingShader.SetFloatParam(cmd, Shader.PropertyToID("g_PathTermination"), (int)settings.pathTermination); + + // To avoid shader permutations, we always need to set an exposure texture, even if we don't read it + if (!preExpose) + { + _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("_ExposureTexture"), _emptyExposureTexture); + } + + SamplingResources.Bind(cmd, _samplingResources); + + // Path-tracer Output + _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_Radiance"), output); + _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_MotionVectors"), motionVectors); + _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_NormalsDepth"), normals); + _rayTracingShader.SetTextureParam(cmd, Shader.PropertyToID("g_DebugOutput"), debugOutput); + + // Path-tracing + RayTracingHelper.ResizeScratchBufferForTrace(_rayTracingShader, (uint)scaledSize.x, (uint)scaledSize.y, 1, ref _traceScratchBuffer); + _rayTracingShader.Dispatch(cmd, _traceScratchBuffer, (uint)scaledSize.x, (uint)scaledSize.y, 1); + + _world.NextFrame(); + } + } + + public void Denoise(CommandBuffer cmd, CommandBufferDenoiser denoiser, float nearClipPlane, float farClipPlane, Matrix4x4 viewProjection, PathTracingSettings settings, RTHandle color, RTHandle normals, RTHandle motionVectors) + { + using (new ProfilingScope(cmd, new ProfilingSampler("LiveGI Denoise"))) + { + // Denoising + if (settings.denoising != DenoiserType.None && denoiser != null) + { + denoiser.DenoiseRequest(cmd, "color", color); + denoiser.DenoiseRequest(cmd, "flow", motionVectors); + denoiser.DenoiseRequest(cmd, "normals", normals); + //if (denoiser is RealTimeDenoiser) + // ((RealTimeDenoiser)denoiser).SetCameraMatrix(cmd, viewProjection, nearClipPlane, farClipPlane, _sceneChanged); + denoiser.GetResults(cmd, color); + } + } + } + + #endregion + + // Private Implementation + #region Private Implementation + + private readonly SamplingResources _samplingResources; + private RayTracingBackend _currentRayTracingBackend; + private RayTracingContext _rayTracingContext; + private IRayTracingShader _rayTracingShader; + private GraphicsBuffer _traceScratchBuffer; + private GraphicsBuffer _buildScratchBuffer; + + // TODO(Yvain) We should use the same buffer for m_TraceScratchBuffer and m_BuildScratchBuffer but that's impractical when we need to + // resize the buffer for both the build and the trace. + // (we can't call Dispose() on a GraphicsBuffer before submitting the CommandBuffer using it) + + private readonly SceneUpdatesTracker _sceneUpdatesTracker; + private bool _sceneChanged = true; + private int _previousLightsHashCode; + + private readonly Material _defaultMaterial; + private World.MaterialDescriptor _defaultMaterialDescriptor; + + private readonly PathTracingOutput _pathTracingOutput; + + private readonly RTHandle _emptyExposureTexture; + + private World _world; + + // This dictionary maps from Unity InstanceID for MeshRenderer or Terrain, to corresponding InstanceHandle for accessing World. + private readonly Dictionary _instanceIDToWorldInstanceHandles = new(); + + // Same as above but for Lights + private readonly Dictionary _instanceIDToWorldLightHandles = new(); + + // Same as above but for Materials + private Dictionary _instanceIDToWorldMaterialHandles = new(); + + // We also keep track of associated material descriptors, so we can free temporary temporary textures when a material is removed + private Dictionary _instanceIDToWorldMaterialDescriptors = new(); + + public static Vector4 GetCameraFrustum(Camera camera) + { + Vector3[] frustumCorners = new Vector3[4]; + camera.CalculateFrustumCorners(new Rect(0, 0, 1, 1), 1.0f, Camera.MonoOrStereoscopicEye.Mono, frustumCorners); + float left = frustumCorners[0].x; + float right = frustumCorners[2].x; + float bottom = frustumCorners[0].y; + float top = frustumCorners[2].y; + + return new Vector4(left, right, bottom, top); + } + + internal static void UpdateMaterials(World world, Dictionary instanceIDToHandle, Dictionary instanceIDToDescriptor, List addedMaterials, List removedMaterials, List changedMaterials) + { + static void DeleteTemporaryTextures(ref World.MaterialDescriptor desc) + { + CoreUtils.Destroy(desc.Albedo); + CoreUtils.Destroy(desc.Emission); + CoreUtils.Destroy(desc.Transmission); + } + + foreach (var materialInstanceID in removedMaterials) + { + // Clean up temporary textures in the descriptor + Debug.Assert(instanceIDToDescriptor.ContainsKey(materialInstanceID)); + var descriptor = instanceIDToDescriptor[materialInstanceID]; + DeleteTemporaryTextures(ref descriptor); + instanceIDToDescriptor.Remove(materialInstanceID); + + // Remove the material from the world + Debug.Assert(instanceIDToHandle.ContainsKey(materialInstanceID)); + world.RemoveMaterial(instanceIDToHandle[materialInstanceID]); + instanceIDToHandle.Remove(materialInstanceID); + } + + foreach (var material in addedMaterials) + { + // Add material to the world + var descriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material); + var handle = world.AddMaterial(in descriptor, UVChannel.UV0); + instanceIDToHandle.Add(material.GetEntityId(), handle); + + // Keep track of the descriptor + instanceIDToDescriptor.Add(material.GetEntityId(), descriptor); + } + + foreach (var material in changedMaterials) + { + // Clean up temporary textures in the old descriptor + Debug.Assert(instanceIDToDescriptor.ContainsKey(material.GetEntityId())); + var oldDescriptor = instanceIDToDescriptor[material.GetEntityId()]; + DeleteTemporaryTextures(ref oldDescriptor); + + // Update the material in the world using the new descriptor + Debug.Assert(instanceIDToHandle.ContainsKey(material.GetEntityId())); + var newDescriptor = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material); + world.UpdateMaterial(instanceIDToHandle[material.GetEntityId()], in newDescriptor, UVChannel.UV0); + instanceIDToDescriptor[material.GetEntityId()] = newDescriptor; + } + } + + private static void UpdateLights(World world, Dictionary instanceIDToHandle, List addedLights, List removedLights, + List changedLights, PathTracingSettings settings, MixedLightingMode mixedLightingMode = MixedLightingMode.IndirectOnly) + { + world.lightPickingMethod = settings.lightPickingMethod; + + // Remove deleted lights + LightHandle[] handlesToRemove = new LightHandle[removedLights.Count]; + for (int i = 0; i < removedLights.Count; i++) + { + var lightInstanceID = removedLights[i]; + handlesToRemove[i] = instanceIDToHandle[lightInstanceID]; + instanceIDToHandle.Remove(lightInstanceID); + } + world.RemoveLights(handlesToRemove); + + // Add new lights + LightHandle[] addedHandles = world.AddLights(Util.ConvertUnityLightsToLightDescriptors(addedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode); + for (int i = 0; i < addedLights.Count; ++i) + instanceIDToHandle.Add(addedLights[i].GetEntityId(), addedHandles[i]); + + // Update changed lights + LightHandle[] handlesToUpdate = new LightHandle[changedLights.Count]; + for (int i = 0; i < changedLights.Count; i++) + handlesToUpdate[i] = instanceIDToHandle[changedLights[i].GetEntityId()]; + world.UpdateLights(handlesToUpdate, Util.ConvertUnityLightsToLightDescriptors(changedLights.ToArray(), settings.multiplyPunctualLightIntensityByPI), settings.respectLightLayers, settings.autoEstimateLUTRange, mixedLightingMode); + } + + internal static void UpdateInstances( + World world, + Dictionary instanceIDToInstanceHandle, + Dictionary instanceIDToMaterialHandle, + List addedInstances, + List changedInstances, + List removedInstances, + RenderedGameObjectsFilter renderedGameObjects, + bool enableEmissiveSampling, + Material fallbackMaterial) + { + foreach (var meshRendererInstanceID in removedInstances) + { + if (instanceIDToInstanceHandle.TryGetValue(meshRendererInstanceID, out InstanceHandle instance)) + { + world.RemoveInstance(instance); + instanceIDToInstanceHandle.Remove(meshRendererInstanceID); + } + else + { + Debug.LogError($"Failed to remove an instance with InstanceID {meshRendererInstanceID}"); + } + } + + foreach (var meshRenderer in addedInstances) + { + if (meshRenderer.isPartOfStaticBatch) + { + Debug.LogError("Static batching should be disabled when using the real time path tracer in play mode. You can disable it from the project settings."); + continue; + } + + var mesh = meshRenderer.GetComponent().sharedMesh; + var localToWorldMatrix = meshRenderer.transform.localToWorldMatrix; + + var materials = Util.GetMaterials(meshRenderer); + var materialHandles = new MaterialHandle[materials.Length]; + bool[] visibility = new bool[materials.Length]; + for (int i = 0; i < materials.Length; i++) + { + if (materials[i] == null) + { + materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()]; + visibility[i] = false; + } + else + { + materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()]; + visibility[i] = true; + } + } + uint[] masks = new uint[materials.Length]; + for (int i = 0; i < masks.Length; i++) + { +#if UNITY_EDITOR + bool hasLightmaps = (meshRenderer.receiveGI == ReceiveGI.Lightmaps); +#else + bool hasLightmaps = true; +#endif + var mask = World.GetInstanceMask(meshRenderer.shadowCastingMode, Util.IsStatic(meshRenderer.gameObject), renderedGameObjects, hasLightmaps); + masks[i] = visibility[i] ? mask : 0u; + } + + + InstanceHandle instance = world.AddInstance( + mesh, + materialHandles, + masks, + 1u << meshRenderer.gameObject.layer, + in localToWorldMatrix, + meshRenderer.bounds, + Util.IsStatic(meshRenderer.gameObject), + renderedGameObjects, + enableEmissiveSampling); + var instanceID = meshRenderer.GetEntityId(); + Debug.Assert(!instanceIDToInstanceHandle.ContainsKey(instanceID)); + instanceIDToInstanceHandle.Add(instanceID, instance); + } + + foreach (var instanceUpdate in changedInstances) + { + try + { + var renderer = instanceUpdate.meshRenderer; + var gameObject = renderer.gameObject; + + if (!instanceIDToInstanceHandle.TryGetValue(renderer.GetEntityId(), out InstanceHandle instance)) + { + Debug.LogError($"Failed to update an instance with InstanceID {instanceUpdate.meshRenderer.GetEntityId()}"); + continue; + } + + if ((instanceUpdate.changes & ModifiedProperties.Transform) != 0) + { + world.UpdateInstanceTransform(instance, gameObject.transform.localToWorldMatrix); + } + + bool materialChanged = (instanceUpdate.changes & ModifiedProperties.Material) != 0; + bool maskPropertiesChanged = (instanceUpdate.changes & ModifiedProperties.IsStatic) != 0 || (instanceUpdate.changes & ModifiedProperties.ShadowCasting) != 0 || (instanceUpdate.changes & ModifiedProperties.Layer) != 0; + if (materialChanged || enableEmissiveSampling || maskPropertiesChanged) + { + var materials = Util.GetMaterials(renderer); + var materialHandles = new MaterialHandle[materials.Length]; + for (int i = 0; i < materials.Length; i++) + { + if (materials[i] == null) + { + materialHandles[i] = instanceIDToMaterialHandle[fallbackMaterial.GetEntityId()]; + } + else + { + materialHandles[i] = instanceIDToMaterialHandle[materials[i].GetEntityId()]; + } + } + + if (materialChanged) + world.UpdateInstanceMaterials(instance, materialHandles); + if (enableEmissiveSampling) + world.UpdateInstanceEmission(instance, instanceUpdate.meshRenderer.GetComponent().sharedMesh, instanceUpdate.meshRenderer.bounds, materialHandles, Util.IsStatic(gameObject), renderedGameObjects); + if (maskPropertiesChanged || materialChanged) + { + bool[] visibility = new bool[materials.Length]; + for (int i = 0; i < materials.Length; i++) + visibility[i] = materials[i] != null; + uint[] masks = new uint[materials.Length]; + for (int i = 0; i < masks.Length; i++) + { +#if UNITY_EDITOR + bool hasLightmaps = (renderer.receiveGI == ReceiveGI.Lightmaps); +#else + bool hasLightmaps = true; +#endif + var mask = World.GetInstanceMask(renderer.shadowCastingMode, Util.IsStatic(renderer.gameObject), renderedGameObjects, hasLightmaps); + masks[i] = visibility[i] ? mask : 0u; + } + world.UpdateInstanceMask(instance, masks); + } + } + } + catch (Exception e) + { + Debug.LogError($"Failed to modify instance {instanceUpdate.meshRenderer.name}: {e}"); + } + } + } +#endregion + } +} +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs.meta similarity index 83% rename from Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs.meta rename to Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs.meta index 4f2a3f1ee86..84f0fc8520b 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs.meta +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingContext.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ff60b2884be449c4c84d8b77cdee9422 +guid: 7dcff21e9e4a5d54ea95b488556ebb9a MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl new file mode 100644 index 00000000000..36afe3c4050 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl @@ -0,0 +1,64 @@ +#ifndef _SAMPLING_PATHTRACINGSAMPLER_HLSL_ +#define _SAMPLING_PATHTRACINGSAMPLER_HLSL_ + +#if defined(QRNG_METHOD_RANDOM_XOR_SHIFT) || defined(QRNG_METHOD_RANDOM_PCG_4D) +#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/PseudoRandom.hlsl" +#else +#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/QuasiRandom.hlsl" +#endif + +// global dimension offset (could be used to alter the noise pattern) +#ifndef QRNG_OFFSET +#define QRNG_OFFSET 0 +#endif + +#ifndef QRNG_SAMPLES_PER_BOUNCE +#define QRNG_SAMPLES_PER_BOUNCE 64 +#endif + +struct PathTracingSampler +{ + #if defined(QRNG_METHOD_SOBOL) + QrngSobol generator; + #elif defined(QRNG_METHOD_SOBOL_BLUE_NOISE) + QrngSobolBlueNoise generator; + #elif defined(QRNG_METHOD_GLOBAL_SOBOL_BLUE_NOISE) + QrngGlobalSobolBlueNoise generator; + #elif defined(QRNG_METHOD_KRONECKER) + QrngKronecker generator; + #elif defined(QRNG_METHOD_RANDOM_XOR_SHIFT) + QrngXorShift generator; + #elif defined(QRNG_METHOD_RANDOM_PCG_4D) + QrngPcg4D generator; + #endif + int bounceIndex; + + void Init(uint2 pixelCoord, uint startPathIndex, uint perPixelPathCount = 256) + { + #if defined(QRNG_METHOD_GLOBAL_SOBOL_BLUE_NOISE) + generator.Init(pixelCoord, startPathIndex, perPixelPathCount); + #else + generator.Init(pixelCoord, startPathIndex); + #endif + bounceIndex = 0; + } + + float GetFloatSample(int dimension) + { + uint actualDimension = QRNG_OFFSET + QRNG_SAMPLES_PER_BOUNCE * bounceIndex + dimension; + return generator.GetFloat(actualDimension); + } + + void NextBounce() + { + bounceIndex++; + } + + void NextPath() + { + generator.NextSample(); + bounceIndex = 0; + } +}; + +#endif // _SAMPLING_PATHTRACINGSAMPLER_HLSL_ diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl.meta new file mode 100644 index 00000000000..48006e65104 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 02eb941b9eeda63448b4714f4522c05a +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs new file mode 100644 index 00000000000..d3f0e45539e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs @@ -0,0 +1,274 @@ +using System; +using Unity.Collections.LowLevel.Unsafe; +using UnityEditor; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; +using static UnityEngine.PathTracing.Core.World; + +namespace UnityEngine.PathTracing.Core +{ + internal class Util + { + internal static class ShaderProperties + { + public static readonly int ScramblingTileXSPP = Shader.PropertyToID("_ScramblingTileXSPP"); + public static readonly int RankingTileXSPP = Shader.PropertyToID("_RankingTileXSPP"); + public static readonly int ScramblingTexture = Shader.PropertyToID("_ScramblingTexture"); + public static readonly int OwenScrambledTexture = Shader.PropertyToID("_OwenScrambledTexture"); + public static readonly int NumLights = Shader.PropertyToID("g_NumLights"); + public static readonly int NumEmissiveMeshes = Shader.PropertyToID("g_NumEmissiveMeshes"); + public static readonly int LightList = Shader.PropertyToID("g_LightList"); + public static readonly int LightFalloff = Shader.PropertyToID("g_LightFalloff"); + public static readonly int LightFalloffLUTRange = Shader.PropertyToID("g_LightFalloffLUTRange"); + public static readonly int LightFalloffLUTLength = Shader.PropertyToID("g_LightFalloffLUTLength"); + public static readonly int MaterialList = Shader.PropertyToID("g_MaterialList"); + public static readonly int AlbedoTextures = Shader.PropertyToID("g_AlbedoTextures"); + public static readonly int EmissionTextures = Shader.PropertyToID("g_EmissionTextures"); + public static readonly int TransmissionTextures = Shader.PropertyToID("g_TransmissionTextures"); + public static readonly int AtlasTexelSize = Shader.PropertyToID("g_AtlasTexelSize"); + public static readonly int PathTracingSkyConditionalResolution = Shader.PropertyToID("_PathTracingSkyConditionalResolution"); + public static readonly int PathTracingSkyMarginalResolution = Shader.PropertyToID("_PathTracingSkyMarginalResolution"); + public static readonly int PathTracingSkyConditionalBuffer = Shader.PropertyToID("_PathTracingSkyConditionalBuffer"); + public static readonly int PathTracingSkyMarginalBuffer = Shader.PropertyToID("_PathTracingSkyMarginalBuffer"); + public static readonly int SceneAccelStruct = Shader.PropertyToID("g_SceneAccelStruct"); + public static readonly int EnvTex = Shader.PropertyToID("g_EnvTex"); + public static readonly int LightEvaluations = Shader.PropertyToID("g_LightEvaluations"); + public static readonly int PathtracerAsGiPreviewMode = Shader.PropertyToID("g_PathtracerAsGiPreviewMode"); + public static readonly int CountNEERayAsPathSegment = Shader.PropertyToID("g_CountNEERayAsPathSegment"); + public static readonly int RenderedInstances = Shader.PropertyToID("g_RenderedInstances"); + public static readonly int PreExpose = Shader.PropertyToID("g_PreExpose"); + public static readonly int BounceCount = Shader.PropertyToID("g_BounceCount"); + public static readonly int MaxIntensity = Shader.PropertyToID("g_MaxIntensity"); + public static readonly int ExposureScale = Shader.PropertyToID("g_ExposureScale"); + public static readonly int LightPickingMethod = Shader.PropertyToID("g_LightPickingMethod"); + public static readonly int IndirectScale = Shader.PropertyToID("g_IndirectScale"); + public static readonly int FrameIndex = Shader.PropertyToID("g_FrameIndex"); + public static readonly int EnableSubPixelJittering = Shader.PropertyToID("g_EnableSubPixelJittering"); + public static readonly int AlbedoBoost = Shader.PropertyToID("g_AlbedoBoost"); + public static readonly int EnvIntensityMultiplier = Shader.PropertyToID("g_EnvIntensityMultiplier"); + public static readonly int ExposureTexture = Shader.PropertyToID("_ExposureTexture"); + public static readonly int CookieAtlas = Shader.PropertyToID("g_CookieAtlas"); + public static readonly int CubemapAtlas = Shader.PropertyToID("g_CubemapAtlas"); + } + + public static void BindLightBuffers(CommandBuffer cmd, IRayTracingShader shader, World world) + { + Debug.Assert(world.LightListBuffer != null); + shader.SetIntParam(cmd, ShaderProperties.LightPickingMethod, (int)world.lightPickingMethod); + shader.SetIntParam(cmd, ShaderProperties.NumLights, world.LightCount); + shader.SetIntParam(cmd, ShaderProperties.NumEmissiveMeshes, world.MeshLightCount); + shader.SetBufferParam(cmd, ShaderProperties.LightList, world.LightListBuffer); + shader.SetBufferParam(cmd, ShaderProperties.LightFalloff, world.LightFalloffBuffer); + shader.SetBufferParam(cmd, ShaderProperties.LightFalloffLUTRange, world.LightFalloffLUTRangeBuffer); + shader.SetIntParam(cmd, ShaderProperties.LightFalloffLUTLength, (int)world.LightFalloffLUTLength); + shader.SetTextureParam(cmd, ShaderProperties.CookieAtlas, world.GetLightCookieTextures()); + shader.SetTextureParam(cmd, ShaderProperties.CubemapAtlas, world.GetLightCubemapTextures()); + world.BindLightAccelerationStructure(cmd, shader); + } + + internal static void BindMaterials(CommandBuffer cmd, IRayTracingShader shader, World world) + { + shader.SetBufferParam(cmd, ShaderProperties.MaterialList, world.GetMaterialListBuffer()); + } + + internal static void BindTextures(CommandBuffer cmd, IRayTracingShader shader, World world) + { + var albedoAtlas = world.GetMaterialAlbedoTextures(); + var emissionAtlas = world.GetMaterialEmissionTextures(); + var transmissionAtlas = world.GetMaterialTransmissionTextures(); + + shader.SetTextureParam(cmd, ShaderProperties.AlbedoTextures, albedoAtlas); + shader.SetTextureParam(cmd, ShaderProperties.EmissionTextures, emissionAtlas); + shader.SetTextureParam(cmd, ShaderProperties.TransmissionTextures, transmissionAtlas); + + if (albedoAtlas != null) + Debug.Assert(albedoAtlas.width == albedoAtlas.height, "Atlas expected to be square"); + if (emissionAtlas != null) + Debug.Assert(emissionAtlas.width == albedoAtlas.width && emissionAtlas.height == albedoAtlas.height, "Atlases expected to have same size"); + if (transmissionAtlas != null) + Debug.Assert(transmissionAtlas.width == albedoAtlas.width && transmissionAtlas.height == albedoAtlas.height, "Atlases expected to have same size"); + + int atlasSize = albedoAtlas?.width ?? 1; + float atlasTexelSize = 1.0f / atlasSize; + shader.SetFloatParam(cmd, ShaderProperties.AtlasTexelSize, atlasTexelSize); + } + + internal static void BindMaterialsAndTextures(CommandBuffer cmd, IRayTracingShader shader, World world) + { + BindMaterials(cmd, shader, world); + BindTextures(cmd, shader, world); + } + + // Helper function to set the skybox CDF resources + internal static void SetEnvSamplingShaderParams(CommandBuffer cmd, IRayTracingShader shader, EnvironmentCDF envCDF) + { + shader.SetIntParam(cmd, ShaderProperties.PathTracingSkyConditionalResolution, envCDF.ConditionalResolution); + shader.SetIntParam(cmd, ShaderProperties.PathTracingSkyMarginalResolution, envCDF.MarginalResolution); + shader.SetBufferParam(cmd, ShaderProperties.PathTracingSkyConditionalBuffer, envCDF.ConditionalBuffer); + shader.SetBufferParam(cmd, ShaderProperties.PathTracingSkyMarginalBuffer, envCDF.MarginalBuffer); + } + + internal static void BindAccelerationStructure(CommandBuffer cmd, IRayTracingShader shader, AccelStructAdapter accel) + { + accel.Bind(cmd, "g_SceneAccelStruct", shader); + } + + internal static void BindWorld(CommandBuffer cmd, IRayTracingShader shader, World world, int skyBoxTextureResolution) + { + BindAccelerationStructure(cmd, shader, world.GetAccelerationStructure()); + BindLightBuffers(cmd, shader, world); + BindMaterialsAndTextures(cmd, shader, world); + + var envTex = world.GetEnvironmentTexture(cmd, skyBoxTextureResolution, out EnvironmentCDF envCDF); + shader.SetTextureParam(cmd, Shader.PropertyToID("g_EnvTex"), envTex); + SetEnvSamplingShaderParams(cmd, shader, envCDF); + } + + static internal void BindPathTracingInputs( + CommandBuffer cmd, + IRayTracingShader shader, + bool countNEERayAsPathSegment, + uint lightEvaluationsPerBounce, + bool preExpose, + int bounces, + float environmentIntensityMultiplier, + RenderedGameObjectsFilter renderedGameObjectsFilter, + SamplingResources samplingResources, + RTHandle emptyTexture) + { + shader.SetIntParam(cmd, ShaderProperties.LightEvaluations, (int)lightEvaluationsPerBounce); + shader.SetIntParam(cmd, ShaderProperties.PathtracerAsGiPreviewMode, 0); + shader.SetIntParam(cmd, ShaderProperties.CountNEERayAsPathSegment, countNEERayAsPathSegment ? 1 : 0); + shader.SetIntParam(cmd, ShaderProperties.RenderedInstances, (int)renderedGameObjectsFilter); + shader.SetIntParam(cmd, ShaderProperties.PreExpose, preExpose ? 1 : 0); + shader.SetIntParam(cmd, ShaderProperties.BounceCount, bounces); + shader.SetIntParam(cmd, ShaderProperties.MaxIntensity, int.MaxValue); + shader.SetFloatParam(cmd, ShaderProperties.ExposureScale, 1.0f); + shader.SetFloatParam(cmd, ShaderProperties.IndirectScale, 1.0f); + shader.SetIntParam(cmd, ShaderProperties.FrameIndex, 0); + shader.SetIntParam(cmd, ShaderProperties.EnableSubPixelJittering, 0); + shader.SetFloatParam(cmd, ShaderProperties.AlbedoBoost, 1.0f); + shader.SetFloatParam(cmd, ShaderProperties.EnvIntensityMultiplier, environmentIntensityMultiplier); + SamplingResources.Bind(cmd, samplingResources); + // To avoid shader permutations, we always need to set an exposure texture, even if we don't read it + if (!preExpose) + { + shader.SetTextureParam(cmd, ShaderProperties.ExposureTexture, emptyTexture); + } + } + + + internal static RayTracingResources LoadOrCreateRayTracingResources() + { + RayTracingResources resources = new RayTracingResources(); +#if UNITY_EDITOR + resources.Load(); +#endif + return resources; + } + + + internal static bool IsStatic(GameObject obj) + { +#if UNITY_EDITOR + // If we are in the editor, access the StaticEditorFlags + int flags = (int)GameObjectUtility.GetStaticEditorFlags(obj); + return (flags & (int)StaticEditorFlags.ContributeGI) != 0; +#else + return obj.isStatic; +#endif + } + + internal static bool IsCookieValid(uint cookieTextureIndex) + { + return cookieTextureIndex != UInt32.MaxValue; + } + + internal static bool IsPunctualLightType(LightType lightType) + { + return lightType == LightType.Directional || lightType == LightType.Spot || lightType == LightType.Point || lightType == LightType.Box; + } + + // Our old baker, LightBaker, multiplied intensities of punctual lights by PI. This isn't quite correct, but it was never changed as it would be breaking. + // The 'multiplyPunctualLightIntensityByPI' can be used to mimic that old behavior. + internal static LightDescriptor[] ConvertUnityLightsToLightDescriptors(Light[] lights, bool multiplyPunctualLightIntensityByPI) + { + LightDescriptor[] lightDescriptors = new LightDescriptor[lights.Length]; + for (int i = 0; i < lights.Length; i++) + { + Light light = lights[i]; + ref LightDescriptor ld = ref lightDescriptors[i]; + ld.Type = light.type; + ld.LinearLightColor = GetLinearLightColor(light); + if (multiplyPunctualLightIntensityByPI && IsPunctualLightType(light.type)) + ld.LinearLightColor *= Mathf.PI; + ld.Shadows = light.shadows; + ld.Transform = light.transform.localToWorldMatrix; + ld.ColorTemperature = light.colorTemperature; +#if UNITY_EDITOR + ld.LightmapBakeType = light.lightmapBakeType; +#else + ld.LightmapBakeType = LightmapBakeType.Baked; +#endif + ld.AreaSize = light.areaSize; + ld.SpotAngle = light.spotAngle; + ld.InnerSpotAngle = light.innerSpotAngle; + ld.CullingMask = (uint)light.cullingMask; + ld.BounceIntensity = light.bounceIntensity; + ld.Range = light.range; + ld.ShadowMaskChannel = -1; + ld.UseColorTemperature = light.useColorTemperature; + ld.FalloffType = Experimental.GlobalIllumination.FalloffType.InverseSquared; // When we extract lights for the path tracer, we assume inverse squared falloff. +#if UNITY_EDITOR + ld.ShadowRadius = Util.IsPunctualLightType(light.type) ? light.shadowRadius : 0.0f; +#else + ld.ShadowRadius = 0.0f; +#endif + ld.CookieTexture = light.cookie; + ld.CookieSize = Math.Max(light.cookieSize2D.x, light.cookieSize2D.y); + } + return lightDescriptors; + } + + // Copy of internal API from Color.cs + private static Color RGBMultiplied(Color color, float multiplier) + { + return new Color(color.r * multiplier, color.g * multiplier, color.b * multiplier, color.a); + } + + private static Vector3 GetLinearLightColor(Light light) + { + Color lightColor = (GraphicsSettings.lightsUseLinearIntensity) ? RGBMultiplied(light.color.linear, light.intensity) : RGBMultiplied(light.color, light.intensity).linear; + lightColor *= light.useColorTemperature ? Mathf.CorrelatedColorTemperatureToRGB(light.colorTemperature) : Color.white; + return new Vector3(lightColor.r, lightColor.g, lightColor.b) * lightColor.a; + } + + internal static Material[] GetMaterials(MeshRenderer renderer) + { + int submeshCount = 1; + var meshFilter = renderer.GetComponent(); + if (meshFilter) + submeshCount = renderer.GetComponent().sharedMesh.subMeshCount; + + Material[] mats = new Material[submeshCount]; + for (int i = 0; i < mats.Length; ++i) + { + if (i < renderer.sharedMaterials.Length && renderer.sharedMaterials[i] != null) + mats[i] = renderer.sharedMaterials[i]; + else + mats[i] = null; + } + + return mats; + } + + internal static ulong EntityIDToUlong(EntityId id) + { + Debug.Assert(UnsafeUtility.SizeOf() == sizeof(int), + "If this assert is firing, the size of EntityId has changed. Remove the intermediate cast to int below, and cast directly to ulong instead."); + + return (ulong)(int)id; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs.meta new file mode 100644 index 00000000000..41dbf7c0d92 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingUtil.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 197834254760f5f458d4f0d320728587 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs new file mode 100644 index 00000000000..5cd96a41de1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs @@ -0,0 +1,299 @@ +using System; +using UnityEngine.LightTransport; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; +using UnityEngine.Rendering.Sampling; +using UnityEngine.PathTracing.Lightmapping; + +namespace UnityEngine.PathTracing.Integration +{ + internal class ProbeIntegratorResources + { + internal IRayTracingShader IndirectShader; + internal IRayTracingShader DirectShader; + internal IRayTracingShader ValidityShader; + internal IRayTracingShader OcclusionShader; + internal SegmentedReduction GatherKernel; + +#if UNITY_EDITOR + public void Load(RayTracingContext context) + { + const string packageFolder = "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/"; + + IndirectShader = context.LoadRayTracingShader(packageFolder + "Shaders/ProbeIntegrationIndirect.urtshader"); + DirectShader = context.LoadRayTracingShader(packageFolder + "Shaders/ProbeIntegrationDirect.urtshader"); + ValidityShader = context.LoadRayTracingShader(packageFolder + "Shaders/ProbeIntegrationValidity.urtshader"); + OcclusionShader = context.LoadRayTracingShader(packageFolder + "Shaders/ProbeIntegrationOcclusion.urtshader"); + GatherKernel = new(SegmentedReduction.LoadShader()); + } +#endif + } + + internal class ProbeIntegrator : IDisposable + { + GraphicsBuffer _positionsBuffer; + SamplingResources _samplingResources; + ProbeIntegratorResources _resourceLibrary; + GraphicsBuffer _traceScratchBuffer; + RTHandle _emptyExposureTexture; + bool _countNEERayAsPathSegment; + private BakeProgressState _progressState; + + // This is a magic number, chosen to be a decent balance between performance and memory usage. + // This will use at maximum (1024 * 1024) * 27 * 4 bytes = 108 MB of VRAM for the expansion buffer when using SH. + const uint maxTotalSamplesPerDispatch = 1024 * 1024; + + private static class ShaderProperties + { + public static readonly int Positions = Shader.PropertyToID("g_Positions"); + public static readonly int ExpansionOffset = Shader.PropertyToID("g_ExpansionOffset"); + public static readonly int PositionsOffset = Shader.PropertyToID("g_PositionsOffset"); + public static readonly int RadianceShl2 = Shader.PropertyToID("g_RadianceShl2"); + public static readonly int RadianceShl2Offset = Shader.PropertyToID("g_RadianceShl2Offset"); + public static readonly int Validity = Shader.PropertyToID("g_Validity"); + public static readonly int ValidityOffset = Shader.PropertyToID("g_ValidityOffset"); + public static readonly int SampleOffset = Shader.PropertyToID("g_SampleOffset"); + public static readonly int SampleCount = Shader.PropertyToID("g_SampleCount"); + public static readonly int Occlusion = Shader.PropertyToID("g_Occlusion"); + public static readonly int OcclusionOffset = Shader.PropertyToID("g_OcclusionOffset"); + public static readonly int PerProbeLightIndices = Shader.PropertyToID("g_PerProbeLightIndices"); + public static readonly int PerProbeLightIndicesOffset = Shader.PropertyToID("g_PerProbeLightIndicesOffset"); + public static readonly int MaxLightsPerProbe = Shader.PropertyToID("g_MaxLightsPerProbe"); + } + + public ProbeIntegrator(bool countNEERayAsPathSegment) + { + _countNEERayAsPathSegment = countNEERayAsPathSegment; + } + + internal void Prepare(GraphicsBuffer positionsBuffer, ProbeIntegratorResources integrationResources, SamplingResources samplingResources) + { + // First release any previously allocated resources, prepare may be called multiple times + ReleaseExistingAllocations(); + + _positionsBuffer = positionsBuffer; + _resourceLibrary = integrationResources; + _samplingResources = samplingResources; + _emptyExposureTexture = RTHandles.Alloc(1, 1, enableRandomWrite: true, name: "Empty EV100 Exposure"); + } + + // We need 2 buffers for expansion and gathering: 1 buffer to store expanded samples, and 1 buffer to use as scratch space for the segmented reduction. + public static void GetScratchBufferSizesInDwords(uint outputStride, uint positionCount, uint sampleCount, out uint expansionBufferSize, out uint reductionBufferSize) + { + uint maxProbesPerDispatch = maxTotalSamplesPerDispatch / sampleCount; + expansionBufferSize = Math.Min(maxTotalSamplesPerDispatch, positionCount * sampleCount) * outputStride; + reductionBufferSize = SegmentedReduction.GetScratchBufferSizeInDwords(sampleCount, outputStride, Math.Min(maxProbesPerDispatch, positionCount)); + } + + public static void GetRadianceScratchBufferSizesInDwords(uint positionCount, uint sampleCount, out uint expansionBufferSize, out uint reductionBufferSize) + => GetScratchBufferSizesInDwords(27, positionCount, sampleCount, out expansionBufferSize, out reductionBufferSize); + + public static void GetValidityScratchBufferSizesInDwords(uint positionCount, uint sampleCount, out uint expansionBufferSize, out uint reductionBufferSize) + => GetScratchBufferSizesInDwords(1, positionCount, sampleCount, out expansionBufferSize, out reductionBufferSize); + + public static void GetOcclusionScratchBufferSizesInDwords(uint maxLightsPerProbe, uint positionCount, uint sampleCount, out uint expansionBufferSize, out uint reductionBufferSize) + => GetScratchBufferSizesInDwords(maxLightsPerProbe, positionCount, sampleCount, out expansionBufferSize, out reductionBufferSize); + + private void DispatchRadianceEstimationKernel( + CommandBuffer cmd, + IRayTracingShader shader, + World world, + uint positionOffset, + uint positionCount, + uint bounceCount, + uint sampleOffset, + uint sampleCount, + uint lightEvaluationPerEvent, + float environmentIntensityMultiplier, + GraphicsBuffer radianceShl2, + uint radianceOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer) + { + Debug.Assert(world.GetAccelerationStructure() != null); + + // General path tracing parameters + bool preExpose = false; + Util.BindPathTracingInputs(cmd, shader, _countNEERayAsPathSegment, lightEvaluationPerEvent, preExpose, (int)bounceCount, environmentIntensityMultiplier, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture); + Util.BindWorld(cmd, shader, world, 32); + + // Zero initialize the output buffer + const uint floatsPerSH = 27; + cmd.SetBufferData(radianceShl2, new float[positionCount * floatsPerSH]); + + DispatchProbeKernel(cmd, shader, positionOffset, positionCount, sampleOffset, sampleCount, floatsPerSH, ShaderProperties.RadianceShl2, radianceShl2, radianceOffset, expansionBuffer, reductionBuffer, + bounceCount); + } + + private void DispatchProbeKernel( + CommandBuffer cmd, + IRayTracingShader shader, + uint positionOffset, + uint positionCount, + uint sampleOffset, + uint sampleCount, + uint outputStride, + int outputBufferPropertyID, + GraphicsBuffer outputBuffer, + uint outputOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer, + uint bounceCount) + { + // Set constant kernel parameters + shader.SetBufferParam(cmd, ShaderProperties.Positions, _positionsBuffer); + shader.SetIntParam(cmd, ShaderProperties.SampleCount, (int)sampleCount); + shader.SetBufferParam(cmd, outputBufferPropertyID, expansionBuffer); + + uint totalSamples = positionCount * sampleCount; + RayTracingHelper.ResizeScratchBufferForTrace(shader, Math.Min(maxTotalSamplesPerDispatch, totalSamples), 1, 1, ref _traceScratchBuffer); + + uint maxProbesPerDispatch = Math.Max(maxTotalSamplesPerDispatch / sampleCount, 1); + + // This outer loop is only here to handle the case where sampleCount > maxTotalSamplesPerDispatch, + // which is possible, but extremely unlikely. In this case, we just run the entire integation loop multiple times. + for (uint sampleWindow = 0; sampleWindow < sampleCount; sampleWindow += maxTotalSamplesPerDispatch) + { + shader.SetIntParam(cmd, ShaderProperties.SampleOffset, (int)sampleOffset + (int)sampleWindow); + + // Loop over chunks of probes, calculate all samples for each chunk. + for (uint probeOffset = 0; probeOffset < positionCount; probeOffset += maxProbesPerDispatch) + { + shader.SetIntParam(cmd, ShaderProperties.ExpansionOffset, (int)probeOffset); + shader.SetIntParam(cmd, ShaderProperties.PositionsOffset, (int)positionOffset + (int)probeOffset); + + // Calculate as many samples as possible given the budget + uint probesToDispatch = Math.Min(maxProbesPerDispatch, positionCount - probeOffset); + uint samplesToDispatch = probesToDispatch * sampleCount; + shader.Dispatch(cmd, _traceScratchBuffer, samplesToDispatch, 1, 1); + + // Perform reduction of each probes samples + _resourceLibrary.GatherKernel.TwoPassSegmentedReduction( + cmd, + sampleCount, + outputStride, + probesToDispatch, + 0, + outputOffset + probeOffset, + expansionBuffer, + reductionBuffer, + outputBuffer, + false); + + // Chip-off work steps based on the current request + ulong workStepsForThisRequest = CalculateWorkSteps(probesToDispatch, sampleCount, bounceCount); + // We need some resource in order to be able to get an async readback, outputBuffer is as good a candidate as any other + cmd.RequestAsyncReadback(outputBuffer, 1, 0, _ => { _progressState.IncrementCompletedWorkSteps(workStepsForThisRequest); }); + + GraphicsHelpers.Flush(cmd); + } + } + } + internal static ulong CalculateWorkSteps(uint probesCount, uint sampleCount, uint bounceCount) => probesCount*sampleCount*(0 == bounceCount ? 1 : bounceCount); + + internal void EstimateIndirectRadianceShl2( + CommandBuffer cmd, + World world, + uint positionOffset, + uint positionCount, + uint bounceCount, + uint sampleOffset, + uint sampleCount, + uint lightEvaluationsPerBounce, + bool ignoreEnvironment, + GraphicsBuffer radianceShl2, + uint radianceOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer) + { + float environmentIntensityMultiplier = ignoreEnvironment ? 0.0f : 1.0f; + DispatchRadianceEstimationKernel(cmd, _resourceLibrary.IndirectShader, world, positionOffset, positionCount, bounceCount, sampleOffset, sampleCount, lightEvaluationsPerBounce, environmentIntensityMultiplier, radianceShl2, radianceOffset, expansionBuffer, reductionBuffer); + } + + internal void EstimateDirectRadianceShl2( + CommandBuffer cmd, + World world, + uint positionOffset, + uint positionCount, + uint sampleOffset, + uint sampleCount, + uint lightEvaluationsPerBounce, + bool ignoreEnvironment, + GraphicsBuffer radianceShl2, + uint radianceOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer) + { + float environmentIntensityMultiplier = ignoreEnvironment ? 0.0f : 1.0f; + DispatchRadianceEstimationKernel(cmd, _resourceLibrary.DirectShader, world, positionOffset, positionCount, 0, sampleOffset, sampleCount, lightEvaluationsPerBounce, environmentIntensityMultiplier, radianceShl2, radianceOffset, expansionBuffer, reductionBuffer); + } + + internal void EstimateValidity( + CommandBuffer cmd, + World world, + uint positionOffset, + uint positionCount, + uint sampleOffset, + uint sampleCount, + GraphicsBuffer validity, + uint validityOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer) + { + var validityShader = _resourceLibrary.ValidityShader; + + // General path tracing parameters + Util.BindAccelerationStructure(cmd, validityShader, world.GetAccelerationStructure()); + Util.BindMaterialsAndTextures(cmd, validityShader, world); + SamplingResources.Bind(cmd, _samplingResources); + + DispatchProbeKernel(cmd, validityShader, positionOffset, positionCount, sampleOffset, sampleCount, 1, ShaderProperties.Validity, validity, validityOffset, expansionBuffer, reductionBuffer, + 0); + } + + internal void EstimateLightOcclusion( + CommandBuffer cmd, + World world, + uint positionOffset, + uint positionCount, + uint sampleOffset, + uint sampleCount, + uint maxLightsPerProbe, + GraphicsBuffer perProbeLightIndices, + uint perProbeLightIndicesOffset, + GraphicsBuffer occlusion, + uint occlusionOffset, + GraphicsBuffer expansionBuffer, + GraphicsBuffer reductionBuffer) + { + var occlusionShader = _resourceLibrary.OcclusionShader; + + // General path tracing parameters + Util.BindWorld(cmd, occlusionShader, world, 32); + SamplingResources.Bind(cmd, _samplingResources); + + occlusionShader.SetBufferParam(cmd, ShaderProperties.PerProbeLightIndices, perProbeLightIndices); + occlusionShader.SetIntParam(cmd, ShaderProperties.PerProbeLightIndicesOffset, (int)perProbeLightIndicesOffset); + occlusionShader.SetIntParam(cmd, ShaderProperties.MaxLightsPerProbe, (int)maxLightsPerProbe); + + DispatchProbeKernel(cmd, occlusionShader, positionOffset, positionCount, sampleOffset, sampleCount, maxLightsPerProbe, ShaderProperties.Occlusion, occlusion, occlusionOffset, expansionBuffer, reductionBuffer, + 0); + } + + private void ReleaseExistingAllocations() + { + _emptyExposureTexture?.Release(); + _traceScratchBuffer?.Dispose(); + _traceScratchBuffer = null; + _samplingResources = null; + } + + public void Dispose() + { + ReleaseExistingAllocations(); + } + + public void SetProgressReporter(BakeProgressState progressState) => _progressState = progressState; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs.meta new file mode 100644 index 00000000000..d665b996d3c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbeIntegrator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fb60b03cc4272524e90a04de8c72b9e1 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs new file mode 100644 index 00000000000..79f36a5eeb1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs @@ -0,0 +1,117 @@ +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.PostProcessing +{ + internal class ProbePostProcessor + { + private ComputeShader _computeShader; + private int _convolveRadianceToIrradianceKernel; + private int _convertToUnityFormatKernel; + private int _addSphericalHarmonicsL2Kernel; + private int _scaleSphericalHarmonicsL2Kernel; + private int _windowSphericalHarmonicsL2Kernel; + + /// + /// Prepares the probe post processor for use. + /// Expects to be given the "ProbePostProcessing.compute" shader. + /// + /// The compute shader containing post processing kernels. Should be the "ProbePostProcessing.compute" shader. + public void Prepare(ComputeShader computeShader) + { + _computeShader = computeShader; + Debug.Assert(_computeShader != null); + _convolveRadianceToIrradianceKernel = _computeShader.FindKernel("ConvolveRadianceToIrradiance"); + Debug.Assert(_convolveRadianceToIrradianceKernel != -1); + _convertToUnityFormatKernel = _computeShader.FindKernel("ConvertToUnityFormat"); + Debug.Assert(_convertToUnityFormatKernel != -1); + _addSphericalHarmonicsL2Kernel = _computeShader.FindKernel("AddSphericalHarmonicsL2"); + Debug.Assert(_addSphericalHarmonicsL2Kernel != -1); + _scaleSphericalHarmonicsL2Kernel = _computeShader.FindKernel("ScaleSphericalHarmonicsL2"); + Debug.Assert(_scaleSphericalHarmonicsL2Kernel != -1); + _windowSphericalHarmonicsL2Kernel = _computeShader.FindKernel("WindowSphericalHarmonicsL2"); + Debug.Assert(_windowSphericalHarmonicsL2Kernel != -1); + } + + public void ConvolveRadianceToIrradiance(CommandBuffer cmd, GraphicsBuffer inRadianceBuffer, GraphicsBuffer outIrradianceBuffer, uint inputOffset, uint outputOffset, uint probeCount) + { + Debug.Assert(_computeShader != null); + Debug.Assert(inRadianceBuffer.count == outIrradianceBuffer.count); + Debug.Assert(inRadianceBuffer.stride == sizeof(float)); + + cmd.SetComputeBufferParam(_computeShader, _convolveRadianceToIrradianceKernel, Shader.PropertyToID("g_PrimaryInputShl2"), inRadianceBuffer); + cmd.SetComputeBufferParam(_computeShader, _convolveRadianceToIrradianceKernel, Shader.PropertyToID("g_OutputShl2"), outIrradianceBuffer); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_PrimaryInputOffset"), (int)inputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_OutputOffset"), (int)outputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_ProbeCount"), (int)probeCount); + + _computeShader.GetKernelThreadGroupSizes(_convolveRadianceToIrradianceKernel, out uint threadGroupsX, out uint _, out uint _); + cmd.DispatchCompute(_computeShader, _convolveRadianceToIrradianceKernel, Mathf.CeilToInt(probeCount / (float)threadGroupsX), 1, 1); + } + + public void ConvertToUnityFormat(CommandBuffer cmd, GraphicsBuffer inIrradianceBuffer, GraphicsBuffer outIrradianceBuffer, uint inputOffset, uint outputOffset, uint probeCount) + { + Debug.Assert(_computeShader != null); + Debug.Assert(inIrradianceBuffer.count == outIrradianceBuffer.count); + Debug.Assert(inIrradianceBuffer.stride == sizeof(float)); + + cmd.SetComputeBufferParam(_computeShader, _convertToUnityFormatKernel, Shader.PropertyToID("g_PrimaryInputShl2"), inIrradianceBuffer); + cmd.SetComputeBufferParam(_computeShader, _convertToUnityFormatKernel, Shader.PropertyToID("g_OutputShl2"), outIrradianceBuffer); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_PrimaryInputOffset"), (int)inputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_OutputOffset"), (int)outputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_ProbeCount"), (int)probeCount); + + _computeShader.GetKernelThreadGroupSizes(_convertToUnityFormatKernel, out uint threadGroupsX, out _, out _); + cmd.DispatchCompute(_computeShader, _convertToUnityFormatKernel, Mathf.CeilToInt(probeCount / (float)threadGroupsX), 1, 1); + } + + internal void AddSphericalHarmonicsL2(CommandBuffer cmd, GraphicsBuffer inA, GraphicsBuffer inB, GraphicsBuffer outSum, uint inputOffsetA, uint inputOffsetB, uint outputOffset, uint probeCount) + { + Debug.Assert(_computeShader != null); + Debug.Assert(inA.count == inB.count); + Debug.Assert(inA.count == outSum.count); + Debug.Assert(inA.stride == sizeof(float)); + + cmd.SetComputeBufferParam(_computeShader, _addSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_PrimaryInputShl2"), inA); + cmd.SetComputeBufferParam(_computeShader, _addSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_SecondaryInputShl2"), inB); + cmd.SetComputeBufferParam(_computeShader, _addSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_OutputShl2"), outSum); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_PrimaryInputOffset"), (int)inputOffsetA); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_SecondaryInputOffset"), (int)inputOffsetB); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_OutputOffset"), (int)outputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_ProbeCount"), (int)probeCount); + + _computeShader.GetKernelThreadGroupSizes(_addSphericalHarmonicsL2Kernel, out uint threadGroupsX, out _, out _); + cmd.DispatchCompute(_computeShader, _addSphericalHarmonicsL2Kernel, Mathf.CeilToInt(probeCount / (float)threadGroupsX), 1, 1); + } + + internal void ScaleSphericalHarmonicsL2(CommandBuffer cmd, GraphicsBuffer input, GraphicsBuffer outScaled, uint inputOffset, uint outputOffset, uint probeCount, float scale) + { + Debug.Assert(_computeShader != null); + Debug.Assert(input.stride == sizeof(float)); + + cmd.SetComputeBufferParam(_computeShader, _scaleSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_PrimaryInputShl2"), input); + cmd.SetComputeBufferParam(_computeShader, _scaleSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_OutputShl2"), outScaled); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_PrimaryInputOffset"), (int)inputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_OutputOffset"), (int)outputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_ProbeCount"), (int)probeCount); + cmd.SetComputeFloatParam(_computeShader, Shader.PropertyToID("g_Scale"), scale); + + _computeShader.GetKernelThreadGroupSizes(_scaleSphericalHarmonicsL2Kernel, out uint threadGroupsX, out _, out _); + cmd.DispatchCompute(_computeShader, _scaleSphericalHarmonicsL2Kernel, Mathf.CeilToInt(probeCount / (float)threadGroupsX), 1, 1); + } + + internal void WindowSphericalHarmonicsL2(CommandBuffer cmd, GraphicsBuffer input, GraphicsBuffer outWindowed, uint inputOffset, uint outputOffset, uint probeCount) + { + Debug.Assert(_computeShader != null); + Debug.Assert(input.stride == sizeof(float)); + + cmd.SetComputeBufferParam(_computeShader, _windowSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_PrimaryInputShl2"), input); + cmd.SetComputeBufferParam(_computeShader, _windowSphericalHarmonicsL2Kernel, Shader.PropertyToID("g_OutputShl2"), outWindowed); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_PrimaryInputOffset"), (int)inputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_OutputOffset"), (int)outputOffset); + cmd.SetComputeIntParam(_computeShader, Shader.PropertyToID("g_ProbeCount"), (int)probeCount); + + _computeShader.GetKernelThreadGroupSizes(_windowSphericalHarmonicsL2Kernel, out uint threadGroupsX, out _, out _); + cmd.DispatchCompute(_computeShader, _windowSphericalHarmonicsL2Kernel, Mathf.CeilToInt(probeCount / (float)threadGroupsX), 1, 1); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs.meta new file mode 100644 index 00000000000..7185c245b1f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ProbePostProcessor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f16318919ba4cf4686265a429896fd9 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline.meta new file mode 100644 index 00000000000..48a89dcd148 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e8fbfd0b5e1d84f8db8d7d684d565b7f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs new file mode 100644 index 00000000000..eaa5932402d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs @@ -0,0 +1,127 @@ +#if ENABLE_PATH_TRACING_SRP +using UnityEngine.Rendering.Denoising; +using UnityEngine.Experimental.Rendering; + +namespace UnityEngine.Rendering.LiveGI +{ + internal class AdditionalCameraData : MonoBehaviour + { + [Header("Viewport Settings")] + [Range(50, 100)] + public uint scale = 100; + + [HideInInspector] + public Matrix4x4 previousViewProjection; + + [HideInInspector] + public int frameIndex; + + [HideInInspector] + public RTHandle rayTracingOutput = null; + + [HideInInspector] + public RTHandle normals = null; + + [HideInInspector] + public RTHandle motionVectors = null; + + [HideInInspector] + public RTHandle debugOutput = null; + + public CommandBufferDenoiser denoiser = null; + + // TODO: Use 32-bit float formats because they are required by the neural network denoising backends. + GraphicsFormat bufferFormat = GraphicsFormat.R32G32B32A32_SFloat; + //GraphicsFormat bufferFormat = GraphicsFormat.R16G16B16A16_SFloat; + + // Start is called before the first frame update + void Start() + { + frameIndex = 0; + previousViewProjection = Matrix4x4.identity; + } + + // Update is called once per frame + void Update() + { + + } + + public void UpdateCameraDataPostRender(Camera camera) + { + previousViewProjection = camera.projectionMatrix * camera.worldToCameraMatrix; + frameIndex++; + } + + public void GetScaledViewport(Camera camera, out int scaledWidth, out int scaledHeight) + { + scaledWidth = Mathf.RoundToInt(camera.pixelWidth * scale / 100.0f); + scaledHeight = Mathf.RoundToInt(camera.pixelHeight * scale / 100.0f); + + // Make sure the scaled viewport size is a multiple of 8 + //scaledWidth = (scaledWidth / 8) * 8; + //scaledHeight = (scaledHeight / 8) * 8; + } + + public void CreatePersistentResources(Camera camera, DenoiserType requestedDenoiser) + { + int scaledWidth = 0; + int scaledHeight = 0; + GetScaledViewport(camera, out scaledWidth, out scaledHeight); + + RTHandles.SetReferenceSize(scaledWidth, scaledHeight); + + if (rayTracingOutput == null) + { + RTHandles.ResetReferenceSize(scaledWidth, scaledHeight); + rayTracingOutput = RTHandles.Alloc(Vector2.one, 1, DepthBits.None, bufferFormat, FilterMode.Point, TextureWrapMode.Repeat, TextureDimension.Tex2D, true, name: "Path tracing render target"); + normals = RTHandles.Alloc(Vector2.one, 1, DepthBits.None, bufferFormat, FilterMode.Point, TextureWrapMode.Repeat, TextureDimension.Tex2D, true, name: "Path tracing normal AOV"); + motionVectors = RTHandles.Alloc(Vector2.one, 1, DepthBits.None, bufferFormat, FilterMode.Point, TextureWrapMode.Repeat, TextureDimension.Tex2D, true, name: "Path tracing motion vectors"); + debugOutput = RTHandles.Alloc(Vector2.one, 1, DepthBits.None, bufferFormat, FilterMode.Point, TextureWrapMode.Repeat, TextureDimension.Tex2D, true, name: "Path tracing debug output"); + + // when we (re)create the output buffer, reset the iteration for the camera + frameIndex = 0; + + // for the first frame we don't have view projection history, so use current camera + previousViewProjection = camera.projectionMatrix * camera.worldToCameraMatrix; + } + + if (denoiser == null || denoiser.type != requestedDenoiser) + { + denoiser = new CommandBufferDenoiser(); + } + + denoiser.Init(requestedDenoiser, scaledWidth, scaledHeight); + + } + + public void ReleaseRTHandles() + { + if (rayTracingOutput != null) + { + rayTracingOutput.Release(); + rayTracingOutput = null; + } + + if (normals != null) + { + normals.Release(); + normals = null; + } + + if (motionVectors != null) + { + motionVectors.Release(); + motionVectors = null; + } + } + + void OnDestroy() + { + ReleaseRTHandles(); + } + } +} + + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs.meta new file mode 100644 index 00000000000..d92f5a21c62 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/AdditionalCameraData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc6e3cdb3dea5e8439d4669bd7fa1d4b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs new file mode 100644 index 00000000000..b260911d303 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs @@ -0,0 +1,29 @@ +#if ENABLE_PATH_TRACING_SRP +using UnityEngine.PathTracing.Core; + +#if ENABLE_PATH_TRACING_SRP +using UnityEngine.Rendering; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +#endif + +namespace UnityEngine.Rendering.LiveGI +{ +#if ENABLE_PATH_TRACING_SRP + // The CreateAssetMenu attribute lets you create instances of this class in the Unity Editor. + [CreateAssetMenu(menuName = "Rendering/RayTracingRenderPipelineAsset")] +#endif + internal class RayTracingRenderPipelineAsset : RenderPipelineAsset + { + public PathTracingSettings settings; + + // Unity calls this method before rendering the first frame. + // If a setting on the Render Pipeline Asset changes, Unity destroys the current Render Pipeline Instance and calls this method again before rendering the next frame. + protected override RenderPipeline CreatePipeline() + { + // Instantiate the Render Pipeline that this custom SRP uses for rendering. + return new RayTracingRenderPipelineInstance(this); + } + } +} +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs.meta new file mode 100644 index 00000000000..873be8c5d76 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 802e0e4104f4e414fa8f25061684c824 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs new file mode 100644 index 00000000000..ad96a49b8c1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs @@ -0,0 +1,79 @@ +#if ENABLE_PATH_TRACING_SRP +using System; +using UnityEngine.PathTracing.Core; + +namespace UnityEngine.Rendering.LiveGI +{ + internal class RayTracingRenderPipelineInstance : RenderPipeline + { + + // Use this variable to a reference to the Render Pipeline Asset that was passed to the constructor + private RayTracingRenderPipelineAsset renderPipelineAsset; + private PathTracingContext ptContext; + + public RayTracingRenderPipelineInstance(RayTracingRenderPipelineAsset asset) + { + renderPipelineAsset = asset; + ptContext = new PathTracingContext(PathTracingOutput.FullPathTracer); + + var resources = Util.LoadOrCreateRayTracingResources(); + if (resources != null) + { + var activeBackend = ptContext.SelectRayTracingBackend(renderPipelineAsset.settings.raytracingBackend, resources); + + // Set the active backend, in case we request an unsuported backend and fallback to another one + renderPipelineAsset.settings.raytracingBackend = activeBackend; + } + } + + protected override void Dispose(bool disposing) + { + ptContext?.Dispose(); + } + + [Obsolete] + protected override void Render(ScriptableRenderContext context, Camera[] cameras) + { + using var cmd = new CommandBuffer(); + cmd.name = "Path Tracing Command Buffer"; + + ptContext.Update(cmd, renderPipelineAsset.settings); + + // Iterate over all Cameras + foreach (Camera camera in cameras) + { + // Update the value of built-in shader variables, based on the current Camera + context.SetupCameraProperties(camera); + + var additionalData = camera.GetComponent(); + if (additionalData == null) + { + additionalData = camera.gameObject.AddComponent(); + additionalData.hideFlags = HideFlags.DontSave; // Don't show this in inspector + } + additionalData.CreatePersistentResources(camera, renderPipelineAsset.settings.denoising); + + Vector4 frustum = PathTracingContext.GetCameraFrustum(camera); + + // Render the path tracing into the camera's target texture + var scaledSize = additionalData.rayTracingOutput.GetScaledSize(); + ptContext.Render(cmd, scaledSize, frustum, camera.cameraToWorldMatrix, camera.worldToCameraMatrix, camera.projectionMatrix, additionalData.previousViewProjection, renderPipelineAsset.settings, additionalData.rayTracingOutput, additionalData.normals, additionalData.motionVectors, additionalData.debugOutput, additionalData.frameIndex); + var viewProjection = camera.projectionMatrix * camera.worldToCameraMatrix; + ptContext.Denoise(cmd,additionalData.denoiser, camera.nearClipPlane, camera.farClipPlane, viewProjection, renderPipelineAsset.settings, additionalData.rayTracingOutput, additionalData.normals, additionalData.motionVectors); + additionalData.UpdateCameraDataPostRender(camera); + + // Blit the path traced frame to the active camera texture + // Note: we could not render to this directly, as it might not have the required UAV flags + cmd.Blit(additionalData.rayTracingOutput, camera.activeTexture); + + // Instruct the graphics API to perform all scheduled commands + context.ExecuteCommandBuffer(cmd); + } + + context.Submit(); + } + + } +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs.meta new file mode 100644 index 00000000000..b005699e948 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/RayTracingRenderPipelineInstance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e0cf68b92a518e43af91d6efe980bfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs new file mode 100644 index 00000000000..c3d6666207f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs @@ -0,0 +1,435 @@ +#if SURFACE_CACHE || ENABLE_PATH_TRACING_SRP + +using System; +using System.Collections.Generic; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityEngine.Rendering.LiveGI +{ + internal class SceneChanges + { + public List addedInstances; + public List changedInstances; + public List removedInstances; + + public List addedMaterials; + public List removedMaterials; + public List changedMaterials; + + public List addedLights; + public List changedLights; + public List removedLights; + + public SceneChanges() + { + addedInstances = new List(); + changedInstances = new List(); + removedInstances = new List(); + + addedMaterials = new List(); + removedMaterials = new List(); + changedMaterials = new List(); + + addedLights = new List(); + changedLights = new List(); + removedLights = new List(); + } + + public bool HasChanges() + { + return (addedInstances.Count | removedInstances.Count | changedInstances.Count + | addedMaterials.Count | removedMaterials.Count | changedMaterials.Count + | addedLights.Count | removedLights.Count | changedLights.Count) != 0; + } + + public void Clear() + { + addedInstances.Clear(); + removedInstances.Clear(); + changedInstances.Clear(); + + addedMaterials.Clear(); + removedMaterials.Clear(); + changedMaterials.Clear(); + + addedLights.Clear(); + removedLights.Clear(); + changedLights.Clear(); + } + } + + [System.Flags] + internal enum ModifiedProperties { Transform = 1, Material = 2, IsStatic = 4, ShadowCasting = 8, Layer = 16 } + internal struct InstanceChanges + { + public MeshRenderer meshRenderer; + public ModifiedProperties changes; + } + + internal class SceneUpdatesTracker : IDisposable + { + struct Timestamp + { + public uint lastVisit; + public uint creation; + } + + class MaterialData + { + public MaterialData(Material material, uint timestamp) + { + this.timestamp.creation = timestamp; + this.timestamp.lastVisit = timestamp; + this.material = material; + metaPassIndex = material.FindPass("Meta"); + shaderCompiled = metaPassIndex != -1 ? ShaderUtil.IsPassCompiled(material, metaPassIndex) : true; + } + + public Material material; + public int metaPassIndex; + public bool shaderCompiled; + + public Timestamp timestamp; + } + + class InstanceData + { + public Timestamp timestamp; + public EntityId[] materialIDs; + public Material[] materials; + public MeshRenderer renderer; + public bool isStatic; + public ShadowCastingMode shadowCastingMode; + } + + class LightData + { + public Light light; + public Timestamp timestamp; + } + + ObjectDispatcher m_ObjectDispatcher; + Dictionary m_Instances; + Dictionary m_Materials; + Dictionary m_Lights; + SceneChanges m_Changes; + uint m_Timestamp; + + + public SceneUpdatesTracker() + { + m_Changes = new SceneChanges(); + m_Instances = new Dictionary(); + m_Materials = new Dictionary(); + m_Lights = new Dictionary(); + + m_ObjectDispatcher = new ObjectDispatcher(); + +#if UNITY_EDITOR + m_ObjectDispatcher.maxDispatchHistoryFramesCount = int.MaxValue; +#endif + m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects); + m_ObjectDispatcher.EnableTransformTracking(ObjectDispatcher.TransformTrackingType.GlobalTRS); + m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects | ObjectDispatcher.TypeTrackingFlags.Assets); + m_ObjectDispatcher.EnableTypeTracking(ObjectDispatcher.TypeTrackingFlags.SceneObjects); + m_ObjectDispatcher.EnableTransformTracking(ObjectDispatcher.TransformTrackingType.GlobalTRS); + } + + public void Dispose() + { + m_ObjectDispatcher.Dispose(); + m_Changes.Clear(); + } + + public SceneChanges GetChanges(bool filterRealtimeLights, bool filterBakedLights, bool filterMixedLights) + { + m_Timestamp++; + m_Changes.Clear(); + + FindInstancesChanges(); + FindMaterialsChanges(); + FindLightChanges(filterRealtimeLights, filterBakedLights, filterMixedLights); + + return m_Changes; + } + + private void FindMaterialsChanges() + { + using var materialChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); + + // Handle added materials + foreach (var instance in m_Instances.Values) + { + foreach (var material in instance.materials) + { + MaterialData data = null; + if (material == null) + continue; + if (m_Materials.TryGetValue(material.GetEntityId(), out data)) + { + data.timestamp.lastVisit = m_Timestamp; + } + else + { + m_Changes.addedMaterials.Add(material); + m_Materials.Add(material.GetEntityId(), new MaterialData(material, m_Timestamp)); + } + } + } + + var justCompiledMaterials = new HashSet(); + + // Handle removed and uncompiled materials + foreach (var materialKeyValue in m_Materials) + { + var materialID = materialKeyValue.Key; + var matData = materialKeyValue.Value; + + if (matData.timestamp.lastVisit != m_Timestamp) + m_Changes.removedMaterials.Add(materialID); +#if UNITY_EDITOR + else if (!matData.shaderCompiled) + { + if (ShaderUtil.IsPassCompiled(matData.material, matData.metaPassIndex)) + { + matData.shaderCompiled = true; + justCompiledMaterials.Add(matData.material); + } + } +#endif + } + + foreach (var key in m_Changes.removedMaterials) + m_Materials.Remove(key); + + // Handle changed materials + foreach (Material changedMaterial in materialChanges.changed) + { + MaterialData data; + if (!m_Materials.TryGetValue(changedMaterial.GetEntityId(), out data)) + continue; + + if (data.timestamp.creation == m_Timestamp) // if this is an instance that has just been added + continue; + + m_Changes.changedMaterials.Add(changedMaterial); + } + +#if UNITY_EDITOR + if (justCompiledMaterials.Count != 0) + { + foreach (var m in m_Changes.changedMaterials) + justCompiledMaterials.Add(m); + + m_Changes.changedMaterials = new List(justCompiledMaterials); + } +#endif + } + + private void FindInstancesChanges() + { + // Handle changed instances + using var meshRendererChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); + var transformChanges = m_ObjectDispatcher.GetTransformChangesAndClear(ObjectDispatcher.TransformTrackingType.GlobalTRS, false); + var changedRenderers = MergeChanges(meshRendererChanges.changed, transformChanges); + + // Handle removed instances + foreach (var key in meshRendererChanges.destroyedID) + { + m_Changes.removedInstances.Add(key); + } + + // Update the remaining timestamps of the active mesh renderers + List keys = new List(m_Instances.Keys); + foreach (var key in keys) + { + if (!m_Instances[key].renderer) + continue; + + if (m_Instances[key].renderer.enabled && m_Instances[key].renderer.gameObject.activeInHierarchy) + { + m_Instances[key].timestamp.lastVisit = m_Timestamp; + } + else + { + m_Changes.removedInstances.Add(key); + } + } + + foreach (var key in m_Changes.removedInstances) + m_Instances.Remove(key); + + + foreach (var item in changedRenderers) + { + var meshRenderer = item.Value.objectReference; + bool tranformChanged = item.Value.transformChanged; + + if (!meshRenderer.enabled || !meshRenderer.gameObject.activeInHierarchy) + { + continue; + } + + InstanceData oldData; + if (!m_Instances.TryGetValue(meshRenderer.GetEntityId(), out oldData)) + { + // This renderer was just added + var newData = CreateInstanceData(m_Timestamp, meshRenderer); + m_Instances.Add(meshRenderer.GetEntityId(), newData); + m_Changes.addedInstances.Add(meshRenderer); + continue; + } + + var data = CreateInstanceData(m_Timestamp, meshRenderer); + + ModifiedProperties changes = 0; + + if (tranformChanged) + changes |= ModifiedProperties.Transform; + + bool IntArraySequenceEqual(EntityId[] firstArray, EntityId[] secondArray) => + ((ReadOnlySpan)firstArray).SequenceEqual(secondArray); + + if (!IntArraySequenceEqual(oldData.materialIDs, data.materialIDs)) + changes |= ModifiedProperties.Material; + + if (oldData.isStatic != data.isStatic) + changes |= ModifiedProperties.IsStatic; + + if (oldData.shadowCastingMode != data.shadowCastingMode) + changes |= ModifiedProperties.ShadowCasting; + + if (changes != 0) + { + m_Changes.changedInstances.Add(new InstanceChanges() + { + changes = changes, + meshRenderer = meshRenderer + }); + + m_Instances[meshRenderer.GetEntityId()] = data; + } + } + } + + InstanceData CreateInstanceData(uint timestamp, MeshRenderer meshRenderer) + { + return new InstanceData() + { + timestamp = new Timestamp { lastVisit = timestamp, creation = timestamp }, + isStatic = meshRenderer.gameObject.isStatic, + materials = meshRenderer.sharedMaterials, + materialIDs = Array.ConvertAll(meshRenderer.sharedMaterials, mat => mat != null ? mat.GetEntityId() : EntityId.None), + shadowCastingMode = meshRenderer.shadowCastingMode, + renderer = meshRenderer, + }; + } + + private void FindLightChanges(bool filterRealtimeLights, bool filterBakedLights, bool filterMixedLights) + { + // Handle changed lights + using var lightChanges = m_ObjectDispatcher.GetTypeChangesAndClear(Unity.Collections.Allocator.Temp); + var lightTransformChanges = m_ObjectDispatcher.GetTransformChangesAndClear(ObjectDispatcher.TransformTrackingType.GlobalTRS, false); + var changedLights = MergeChanges(lightChanges.changed, lightTransformChanges); + + // Handle removed lights + foreach (var key in lightChanges.destroyedID) + { + if (m_Lights.ContainsKey(key)) + m_Changes.removedLights.Add(key); + } + + // Update the remaining timestamps of the active lights + List keys = new List(m_Lights.Keys); + foreach (var key in keys) + { + if (!m_Lights[key].light) + continue; + + bool isRealtimeLight = m_Lights[key].light.lightmapBakeType == LightmapBakeType.Realtime; + bool isBakedLight = m_Lights[key].light.lightmapBakeType == LightmapBakeType.Baked; + bool isMixedLight = m_Lights[key].light.lightmapBakeType == LightmapBakeType.Mixed; + + if (m_Lights[key].light.enabled && m_Lights[key].light.gameObject.activeInHierarchy && !(filterRealtimeLights && isRealtimeLight) && !(filterBakedLights && isBakedLight) && !(filterMixedLights && isMixedLight)) + { + m_Lights[key].timestamp.lastVisit = m_Timestamp; + } + else + { + m_Changes.removedLights.Add(key); + } + } + + foreach (var key in m_Changes.removedLights) + m_Lights.Remove(key); + + + foreach (var item in changedLights) + { + var light = item.Value.objectReference; + + bool isRealtimeLight = light.lightmapBakeType == LightmapBakeType.Realtime; + bool isBakedLight = light.lightmapBakeType == LightmapBakeType.Baked; + bool isMixedLight = light.lightmapBakeType == LightmapBakeType.Mixed; + + if (!light.enabled || !light.gameObject.activeInHierarchy || (filterRealtimeLights && isRealtimeLight) && !(filterBakedLights && isBakedLight) && !(filterMixedLights && isMixedLight)) + { + continue; + } + + var newData = CreateLightData(m_Timestamp, light); + + // Newly added lights + if (!m_Lights.ContainsKey(light.GetEntityId())) + { + m_Lights.Add(light.GetEntityId(), newData); + m_Changes.addedLights.Add(light); + continue; + } + + // Changed lights + m_Changes.changedLights.Add(light); + m_Lights[light.GetEntityId()] = newData; + } + } + + LightData CreateLightData(uint timestamp, Light light) + { + return new LightData() + { + timestamp = new Timestamp { lastVisit = timestamp, creation = timestamp }, + light = light, + }; + } + + struct ChangedObject + where T : Component + { + public T objectReference; + public bool transformChanged; + } + + Dictionary> MergeChanges(Object[] changedRenderers, Component[] changedTransforms) + where T : Component + { + var map = new Dictionary>(); + + foreach (Component component in changedTransforms) + { + map.TryAdd(component.GetEntityId(), new ChangedObject() { objectReference = (T)component, transformChanged = true }); + } + + foreach (Component component in changedRenderers) + { + map.TryAdd(component.GetEntityId(), new ChangedObject() { objectReference = (T)component, transformChanged = false }); + } + + return map; + } + } +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs.meta new file mode 100644 index 00000000000..b4e4bceadee --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/RenderPipeline/SceneUpdatesTracker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6dfa231fae65b4484af2f729153f6544 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs new file mode 100644 index 00000000000..13a14f5c492 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs @@ -0,0 +1,145 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Core +{ + internal class SegmentedReduction + { + private readonly ComputeShader _segmentedReductionShader; + private readonly int _reductionKernel; + private readonly uint _threadGroupSize; + + private static class ShaderProperties + { + public static readonly int SegmentWidth = Shader.PropertyToID("g_SegmentWidth"); + public static readonly int SegmentStride = Shader.PropertyToID("g_SegmentStride"); + public static readonly int SegmentCount = Shader.PropertyToID("g_SegmentCount"); + public static readonly int InputOffset = Shader.PropertyToID("g_InputOffset"); + public static readonly int OutputOffset = Shader.PropertyToID("g_OutputOffset"); + public static readonly int OverwriteOutput = Shader.PropertyToID("g_OverwriteOutput"); + public static readonly int TruncateInterval = Shader.PropertyToID("g_TruncateInterval"); + public static readonly int TruncatedSegmentWidth = Shader.PropertyToID("g_TruncatedSegmentWidth"); + public static readonly int InputFloatBuffer = Shader.PropertyToID("g_InputFloatBuffer"); + public static readonly int OutputFloatBuffer = Shader.PropertyToID("g_OutputFloatBuffer"); + } + + public SegmentedReduction(ComputeShader segmentedReductionShader) + { + _segmentedReductionShader = segmentedReductionShader; + _reductionKernel = _segmentedReductionShader.FindKernel("SegmentedReductionFloat"); + _segmentedReductionShader.GetKernelThreadGroupSizes(_reductionKernel, out _threadGroupSize, out _, out _); + } + +#if UNITY_EDITOR + public static ComputeShader LoadShader() + { + return UnityEditor.AssetDatabase.LoadAssetAtPath("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute"); + } +#endif + + private static void CalculateParametersForTwoPassReduction( + uint segmentWidth, + uint segmentCount, + out uint firstPassSegmentCount, + out uint firstPassSegmentWidth, + out uint truncateInterval, + out uint truncatedSegmentWidth, + out uint secondPassSegmentCount, + out uint secondPassSegmentWidth) + { + // Split each segments into a number of segments with size close to sqrt(segmentWidth). + firstPassSegmentWidth = (uint)Mathf.Floor(Mathf.Sqrt(segmentWidth)); + + // Splitting may result in a truncated segment at the end of each original segment. + // Calculate how often this happen, how wide the truncated segment will be. + truncateInterval = GraphicsHelpers.DivUp(segmentWidth, firstPassSegmentWidth); + truncatedSegmentWidth = segmentWidth % firstPassSegmentWidth; + + // Calculate total number of segments after splitting. + firstPassSegmentCount = truncateInterval * segmentCount; + + // The second pass reduces the output of the first pass. + // The first pass will produce segmentCount segments of width truncateInterval. + secondPassSegmentCount = segmentCount; + secondPassSegmentWidth = truncateInterval; + } + + public static uint GetScratchBufferSizeInDwords(uint segmentWidth, uint segmentStride, uint segmentCount) + { + CalculateParametersForTwoPassReduction(segmentWidth, segmentCount, out uint firstPassSegmentCount, out _, out _, out _, out _, out _); + return firstPassSegmentCount * segmentStride; + } + + // Performs segmented reduction in 2 passes, each doing roughly sqrt(N) reductions. + // This is almost always faster than single pass reduction, and should be preferred over it. + public void TwoPassSegmentedReduction( + CommandBuffer cmd, + uint segmentWidth, + uint segmentStride, + uint segmentCount, + uint inputOffset, + uint outputOffset, + GraphicsBuffer inputBuffer, + GraphicsBuffer scratchBuffer, + GraphicsBuffer outputBuffer, + bool overwriteOutput) + { + CalculateParametersForTwoPassReduction( + segmentWidth, + segmentCount, + out uint firstPassSegmentCount, + out uint firstPassSegmentWidth, + out uint truncateInterval, + out uint truncatedSegmentWidth, + out uint secondPassSegmentCount, + out uint secondPassSegmentWidth); + + DispatchReductionKernel(cmd, firstPassSegmentWidth, segmentStride, firstPassSegmentCount, inputOffset, 0, inputBuffer, scratchBuffer, true, truncateInterval, truncatedSegmentWidth); + DispatchReductionKernel(cmd, secondPassSegmentWidth, segmentStride, secondPassSegmentCount, inputOffset, outputOffset, scratchBuffer, outputBuffer, overwriteOutput, 0, 0); + } + + // Performs segmented reduction in a single pass. This is slower than the 2 pass + // variant in most cases, except for the case where each individual reduction is very small. + // This function is mostly intended for small problem sizes and for debugging. + public void SinglePassSegmentedReduction( + CommandBuffer cmd, + uint segmentWidth, + uint segmentStride, + uint segmentCount, + uint inputOffset, + uint outputOffset, + GraphicsBuffer inputBuffer, + GraphicsBuffer outputBuffer, + bool overwriteOutput) + { + DispatchReductionKernel(cmd, segmentWidth, segmentStride, segmentCount, inputOffset, outputOffset, inputBuffer, outputBuffer, overwriteOutput, 0, 0); + } + + private void DispatchReductionKernel( + CommandBuffer cmd, + uint segmentWidth, + uint segmentStride, + uint segmentCount, + uint inputOffset, + uint outputOffset, + GraphicsBuffer inputBuffer, + GraphicsBuffer outputBuffer, + bool overwriteOutput, + uint truncateInterval, + uint truncatedSegmentWidth) + { + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.SegmentWidth, (int)segmentWidth); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.SegmentStride, (int)segmentStride); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.SegmentCount, (int)segmentCount); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.InputOffset, (int)inputOffset); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.OutputOffset, (int)outputOffset); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.OverwriteOutput, overwriteOutput ? 1 : 0); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.TruncateInterval, (int)truncateInterval); + cmd.SetComputeIntParam(_segmentedReductionShader, ShaderProperties.TruncatedSegmentWidth, (int)truncatedSegmentWidth); + cmd.SetComputeBufferParam(_segmentedReductionShader, _reductionKernel, ShaderProperties.InputFloatBuffer, inputBuffer); + cmd.SetComputeBufferParam(_segmentedReductionShader, _reductionKernel, ShaderProperties.OutputFloatBuffer, outputBuffer); + cmd.DispatchCompute(_segmentedReductionShader, _reductionKernel, (int)GraphicsHelpers.DivUp(segmentCount, _threadGroupSize), 1, 1); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs.meta new file mode 100644 index 00000000000..209aacd6acf --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SegmentedReduction.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2502941332b91fa4a8a2804e7ecb19ac diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs new file mode 100644 index 00000000000..4c20762a3c3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs @@ -0,0 +1,24 @@ + +namespace UnityEngine.PathTracing.Core +{ + internal static class ShaderProperties + { + public static readonly int LightGrid = Shader.PropertyToID("g_LightGrid"); + public static readonly int LightGridCellsData = Shader.PropertyToID("g_LightGridCellsData"); + public static readonly int GridDimX = Shader.PropertyToID("g_GridDimX"); + public static readonly int GridDimY = Shader.PropertyToID("g_GridDimY"); + public static readonly int GridDimZ = Shader.PropertyToID("g_GridDimZ"); + public static readonly int NumLights = Shader.PropertyToID("g_NumLights"); + public static readonly int GridMin = Shader.PropertyToID("g_GridMin"); + public static readonly int GridSize = Shader.PropertyToID("g_GridSize"); + public static readonly int CellSize = Shader.PropertyToID("g_CellSize"); + public static readonly int InvCellSize = Shader.PropertyToID("g_InvCellSize"); + public static readonly int TotalReservoirCount = Shader.PropertyToID("g_TotalLightsInGridCount"); + public static readonly int BuildPass = Shader.PropertyToID("g_BuildPass"); + public static readonly int NumCandidates = Shader.PropertyToID("g_NumCandidates"); + public static readonly int NumReservoirs = Shader.PropertyToID("g_NumReservoirs"); + public static readonly int MaxLightsPerCell = Shader.PropertyToID("g_MaxLightsPerCell"); + public static readonly int LightList = Shader.PropertyToID("g_LightList"); + public static readonly int NumEmissiveMeshes = Shader.PropertyToID("g_NumEmissiveMeshes"); + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs.meta new file mode 100644 index 00000000000..9ba5df011c2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/ShaderProperties.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: da922357540ba0d44ba6435efbaa8da4 diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders.meta new file mode 100644 index 00000000000..3e0ed350f4f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7cd12a213ac4f5f4d917aa18d3a538ce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute new file mode 100644 index 00000000000..d5bc3e62af2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute @@ -0,0 +1,26 @@ +#define GROUP_SIZE_X 8 +#define GROUP_SIZE_Y 8 + + +Texture2D g_Source; +RWTexture2D g_Destination; + +SamplerState sampler_g_Source; + +uint g_TextureWidth; +uint g_TextureHeight; + +// Projects (blits) a face of a cubemap on a 2D texture +#pragma kernel BlitGrayScaleCookie +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)] +void BlitGrayScaleCookie(in uint2 gidx : SV_DispatchThreadID) +{ + if (gidx.x >= g_TextureWidth || gidx.y >= g_TextureHeight) + return; + + + float2 texCoord = float2((gidx.x + 0.5f) / g_TextureWidth, (gidx.y + 0.5f) / g_TextureHeight); + + float c = g_Source.SampleLevel(sampler_g_Source, texCoord, 0).w; + g_Destination[gidx] = float4(c, c, c, 1); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute.meta new file mode 100644 index 00000000000..8e0a462625e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCookie.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 557fa399e33bf7647bda5697c5c158df +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute new file mode 100644 index 00000000000..20fdb1f8002 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute @@ -0,0 +1,66 @@ +#define GROUP_SIZE_X 8 +#define GROUP_SIZE_Y 8 + +#pragma multi_compile _ GRAYSCALE_BLIT + +TextureCube g_Source; +RWTexture2D g_Destination; + +SamplerState sampler_g_Source; + +int g_TextureSize; +int g_Face; + +#define PositiveX 0 +#define NegativeX 1 +#define PositiveY 2 +#define NegativeY 3 +#define PositiveZ 4 +#define NegativeZ 5 + +// Projects (blits) a face of a cubemap on a 2D texture +#pragma kernel BlitCubemap +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)] +void BlitCubemap(in uint2 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= uint(g_TextureSize) || gidx.y >= uint(g_TextureSize)) + return; + + float u = (gidx.x + 0.5f) / g_TextureSize; + float v = (gidx.y + 0.5f) / g_TextureSize; + + u = 2 * u - 1.0f; + v = 1.0f - 2 * v; + + float3 texCoord = 0; + switch (g_Face) + { + case PositiveX: + texCoord = float3(1, v, -u); + break; + case NegativeX: + texCoord = float3(-1, v, u); + break; + case PositiveY: + texCoord = float3(u, 1, -v); + break; + case NegativeY: + texCoord = float3(u, -1, v); + break; + case PositiveZ: + texCoord = float3(u, v, 1); + break; + case NegativeZ: + texCoord = float3(-u, v, -1); + break; + } + + float4 c = g_Source.SampleLevel(sampler_g_Source, texCoord, 0); + +#if defined(GRAYSCALE_BLIT) + g_Destination[gidx] = float4(c.w, c.w, c.w, 1); +#else + g_Destination[gidx] = float4(c.r, c.g, c.b, 1); +#endif +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute.meta new file mode 100644 index 00000000000..d4c923bb6ea --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BlitCubemap.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5a992812cb320d146a66cc600200cce7 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute new file mode 100644 index 00000000000..08e2f5c7d9d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute @@ -0,0 +1,267 @@ + +#pragma multi_compile _ SPARSE_GRID + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl" + +#include "PathTracingCommon.hlsl" +#include "PathTracingRandom.hlsl" +#include "PathTracingLightGrid.hlsl" + +#define GROUP_SIZE_X 4 +#define GROUP_SIZE_Y 4 +#define GROUP_SIZE_Z 4 +#define MAX_RESERVOIRS_PER_VOXEL 64 + +groupshared float s_SampleCount[GROUP_SIZE_X * GROUP_SIZE_Y * GROUP_SIZE_Z * MAX_RESERVOIRS_PER_VOXEL]; + +#ifndef PI +#define PI 3.14159265358979323846f +#endif + +// Input parameters and lighting state +StructuredBuffer g_LightList; +uint g_NumLights; +uint g_MaxLightsPerCell; +uint g_NumCandidates; +uint g_BuildPass; + +// Output indices and grid +RWStructuredBuffer g_LightGridCellsData; +RWStructuredBuffer g_LightGrid; +RWStructuredBuffer g_TotalLightsInGridCount; + +// Source: https://bartwronski.com/2017/04/13/cull-that-cone/ +bool TestConeVsSphere(in float3 origin, in float3 forward, in float size, in float angle, in float4 testSphere) +{ + const float3 V = testSphere.xyz - origin; + const float VlenSq = dot(V, V); + const float V1len = dot(V, forward); + const float distanceClosestPoint = cos(angle) * sqrt(VlenSq - V1len * V1len) - V1len * sin(angle); + + const bool angleCull = distanceClosestPoint > testSphere.w; + const bool frontCull = V1len > testSphere.w + size; + const bool backCull = V1len < -testSphere.w; + return !(angleCull || frontCull || backCull); +} + +// Estimate the maximum emission in a voxel / Target function for RIS +float3 EstimateEmission(PTLight light, float3 worldPosition) +{ + const float Deg2Rad = PI * 2.0f / 360.0f; + float3 emission = float3(1.0f, 1.0f, 1.0f); + + if (light.type == DIRECTIONAL_LIGHT) + // Directional lights affect all cells + emission = light.intensity; + else if (light.type == ENVIRONMENT_LIGHT) + // Environment light affects all cells + // For conservtive culling, returning any positive value is enough, but for ReGIR we can improve the sampling with a better estimate + emission = float3(1.0f, 1.0f, 1.0f); + else if ((light.type == POINT_LIGHT) || (light.type == SPOT_LIGHT)) + { + // First compute the point light attenuation + float d = length(light.position - worldPosition); + + // To be conservative on the attenuation, we substruct the cell radius of the grid + d = max(0.01f, d - GetCellSize()); + + // Analytical range attenuation from SRP core + float attenuation = SmoothWindowedDistanceAttenuation(d * d, rcp(d), light.attenuation.x, light.attenuation.y); + + if (light.type == SPOT_LIGHT) + { + // For spotlights, we also do a conservative anglular atenuation: 0 if the spotlight cone does not intersect the voxel, 1 otherwise. + float4 bSphere = float4(worldPosition, 0.5 * GetCellSize()); + bool intersection = TestConeVsSphere(light.position, light.forward, light.range, 0.5 * light.spotAngle.x * Deg2Rad, bSphere); + attenuation = intersection ? attenuation : 0.0f; + } + + emission = attenuation * light.intensity; + } + else if (light.type == RECTANGULAR_LIGHT || light.type == DISC_LIGHT) + { + // First compute the point light attenuation + float d = length(light.position - worldPosition); + + // To be conservative on the attenuation, we substruct the cell radius of the grid. + // We also substract the radius (of the bounding sphere) of the light + float r = light.type == DISC_LIGHT ? light.width : 0.5f * sqrt(light.width * light.width + light.height * light.height); + d = max(0.01f, d - GetCellSize() - r); + + // Analytical range attenuation from SRP core + float attenuation = SmoothWindowedDistanceAttenuation(d * d, rcp(d), light.attenuation.x, light.attenuation.y); + + // The analytical lights are single sided. To properly do the conservative culling, we re-use the spotlight culling code + float4 bSphere = float4(worldPosition, 0.5 * GetCellSize()); + const float angle = 0.5f * 180.0f * Deg2Rad; + bool intersection = TestConeVsSphere(light.position, light.forward, light.range, angle, bSphere); + attenuation = intersection ? attenuation : 0.0f; + + emission = attenuation * light.intensity; + } + else if (light.type == BOX_LIGHT) + { + // For box ligths we only do some basic culling based on the bounding sphere of the cell and the near/far plane, reusing the existing code + // Note: box lights/projectors don't have range attenuation + float4 bSphere = float4(worldPosition, 0.5 * GetCellSize()); + const float angle = 0.5f * 180.0f * Deg2Rad; + bool intersection = TestConeVsSphere(light.position, light.forward, light.range, angle, bSphere); + float attenuation = intersection ? 1.0f : 0.0f; + + emission = attenuation * light.intensity; + } + + return emission; +} + + +float GetLightTargetFunction(float3 pos, uint lightIndex) +{ + PTLight light = g_LightList[lightIndex]; + float3 emission = EstimateEmission(light, pos); + return Luminance(emission); +} + + +#pragma kernel BuildConservativeLightGrid +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, GROUP_SIZE_Z)] +void BuildConservativeLightGrid(in uint3 gidx : SV_DispatchThreadID, in uint threadIndex : SV_GroupIndex) +{ + if (gidx.x >= g_GridDimX || gidx.y >= g_GridDimY || gidx.z >= g_GridDimZ) + return; + + uint cellIndex = GetCellIndex(gidx); + + PathTracingSampler rngState; + rngState.Init(gidx.xy, gidx.z); + + float3 pos = GetCellPosition(gidx); + + if (g_BuildPass == 0) // compute number of lights overlapping current cell + { + uint lightCount = 0; + for (uint lightIndex = 0; lightIndex < g_NumLights; ++lightIndex) + { + if (GetLightTargetFunction(pos, lightIndex) > 0) + lightCount++; + } + + uint unusedOldValue; + InterlockedAdd(g_TotalLightsInGridCount[0], lightCount, unusedOldValue); + g_LightGrid[cellIndex] = uint2(0, lightCount); + } + else // write lights overlapping current cell to g_LightGridCellsData + { + #if SPARSE_GRID + uint lightCount = g_LightGrid[cellIndex].y; + uint firstCellLightDataIndex; + InterlockedAdd(g_TotalLightsInGridCount[0], lightCount, firstCellLightDataIndex); + g_LightGrid[cellIndex].x = firstCellLightDataIndex; + uint cellDataStart = firstCellLightDataIndex; + #else + uint cellDataStart = cellIndex * g_MaxLightsPerCell; + #endif + uint lightDataIndex = 0; + + for (uint lightIndex = 0; lightIndex < g_NumLights; ++lightIndex) + { + // Conservative culling mode: if there is a contribution, save the light index / reservoir + // This should be used when the number of candidates equals the number of ligths + if (GetLightTargetFunction(pos, lightIndex) > 0) + { + ThinReservoir reservoir = { lightIndex, 1.0f}; + g_LightGridCellsData[cellDataStart + lightDataIndex] = reservoir; + lightDataIndex++; + } + + #if !(SPARSE_GRID) + if (lightDataIndex >= g_MaxLightsPerCell) + break; + #endif + } + + #if !(SPARSE_GRID) + uint lightCount = lightDataIndex; + g_LightGrid[cellIndex] = uint2(cellDataStart, lightCount); + #endif + } +} + +void UpdateReservoir(int reservoirIndex, int lightIndex, float targetFunction, float r) +{ + float candidateRISWeight = targetFunction; + + ThinReservoir reservoir = g_LightGridCellsData[reservoirIndex]; + float totalWeights = reservoir.weight + candidateRISWeight; + + if (r * totalWeights < candidateRISWeight) + { + reservoir.lightIndex = lightIndex; + reservoir.weight = totalWeights; + } + + g_LightGridCellsData[reservoirIndex] = reservoir; +} + +void NormalizeReservoirWeights(uint offset, float3 pos, uint numReservoirs, uint threadIndex) +{ + for (uint i = 0; i < numReservoirs; ++i) + { + ThinReservoir reservoir = g_LightGridCellsData[offset + i]; + float numSamples = s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + i]; + + float sampleTargetDensity = GetLightTargetFunction(pos, reservoir.lightIndex); + float risSampleWeight = (reservoir.weight / numSamples) / sampleTargetDensity; + reservoir.weight = risSampleWeight; + g_LightGridCellsData[offset + i] = reservoir; + } +} + +#pragma kernel BuildRegirLightGrid +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, GROUP_SIZE_Z)] +void BuildRegirLightGrid(in uint3 gidx : SV_DispatchThreadID, in uint threadIndex : SV_GroupIndex) +{ + if (gidx.x >= g_GridDimX || gidx.y >= g_GridDimY || gidx.z >= g_GridDimZ) + return; + + uint cellIndex = GetCellIndex(gidx); + + PathTracingSampler rngState; + rngState.Init(gidx.xy, gidx.z); + + float3 pos = GetCellPosition(gidx); + + uint reservoirIndex = 0; + for (uint lightIndex = 0; lightIndex < g_NumCandidates; ++lightIndex) + { + float targetFunction = GetLightTargetFunction(pos, lightIndex); + + // Conservative culling mode: if there is a contribution, save the light index / reservoir + // This should be used when the number of candidates equals the number of ligths + if (targetFunction > 0) + { + ThinReservoir reservoir = { lightIndex, targetFunction }; + + if (reservoirIndex < g_NumReservoirs) + { + g_LightGridCellsData[cellIndex * g_NumReservoirs + reservoirIndex] = reservoir; + s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + reservoirIndex] = 1.0f; + reservoirIndex++; + } + else + { + // When we run out of free reservoirs, we rely on reservoir sampling to update an existing one + // The reservoirs are stratified based on the lightIndex % g_NumReservoirs + uint reservoirLocalIndex = lightIndex % max(g_NumReservoirs, 1); + uint reservoirGlobalIndex = cellIndex * g_NumReservoirs + reservoirLocalIndex; + float rnd = rngState.GetFloatSample(lightIndex); + UpdateReservoir(reservoirGlobalIndex, lightIndex, targetFunction, rnd); + s_SampleCount[threadIndex * MAX_RESERVOIRS_PER_VOXEL + reservoirLocalIndex] += 1; + } + } + } + + NormalizeReservoirWeights(cellIndex * g_NumReservoirs, pos, reservoirIndex, threadIndex); + g_LightGrid[cellIndex] = uint2(cellIndex * g_NumReservoirs, reservoirIndex); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute.meta new file mode 100644 index 00000000000..27829e171b3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/BuildLightGrid.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 16e47c1641bd0104e92b624601457bb0 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute new file mode 100644 index 00000000000..63986d8fe14 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute @@ -0,0 +1,183 @@ +RWTexture2D g_TextureInOut; +int g_TextureWidth; +int g_TextureHeight; + +float4 g_MultiplicationFactor; +#pragma kernel Multiply +[numthreads(8,8,1)] +void Multiply(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + g_TextureInOut[id.xy] = g_TextureInOut[id.xy] * g_MultiplicationFactor; +} + +int g_ChannelIndex; +#pragma kernel BroadcastChannel +[numthreads(8,8,1)] +void BroadcastChannel(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + if (g_ChannelIndex < 0 || g_ChannelIndex > 3) + return; + g_TextureInOut[id.xy] = g_TextureInOut[id.xy][g_ChannelIndex]; +} + +float g_ChannelValue; +#pragma kernel SetChannel +[numthreads(8,8,1)] +void SetChannel(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + if (g_ChannelIndex < 0 || g_ChannelIndex > 3) + return; + switch (g_ChannelIndex) + { + case 0: + g_TextureInOut[id.xy] = float4(g_ChannelValue, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a); + break; + case 1: + g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_ChannelValue, g_TextureInOut[id.xy].b, g_TextureInOut[id.xy].a); + break; + case 2: + g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_ChannelValue, g_TextureInOut[id.xy].a); + break; + case 3: + g_TextureInOut[id.xy] = float4(g_TextureInOut[id.xy].r, g_TextureInOut[id.xy].g, g_TextureInOut[id.xy].b, g_ChannelValue); + break; + } +} + +Texture2D g_SourceTexture; +SamplerState g_LinearClampSampler; +RWTexture2D g_TextureOut; +int g_BoxFilterRadius; +#pragma kernel ReferenceBoxFilter +[numthreads(8,8,1)] +void ReferenceBoxFilter(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + float4 sum = float4(0, 0, 0, 0); + for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y) + { + for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x) + { + float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight); + sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0); + } + } + g_TextureOut[id.xy] = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1)); +} + +#pragma kernel ReferenceBoxFilterBlueChannel +[numthreads(16,8,1)] +void ReferenceBoxFilterBlueChannel(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + float4 color = g_SourceTexture[id.xy]; + float sum = 0; + for (int y = -g_BoxFilterRadius; y <= g_BoxFilterRadius; ++y) + { + for (int x = -g_BoxFilterRadius; x <= g_BoxFilterRadius; ++x) + { + float2 uv = (float2(id.xy) + float2(x, y)) / float2(g_TextureWidth, g_TextureHeight); + sum += g_SourceTexture.SampleLevel(g_LinearClampSampler, uv, 0).b; + } + } + float average = sum / float((g_BoxFilterRadius * 2 + 1) * (g_BoxFilterRadius * 2 + 1)); + g_TextureOut[id.xy] = float4(color.rg, average, color.a); +} + +Texture2D g_SampleCountInW; +Texture2D g_VarianceInR; +#pragma kernel StandardError +[numthreads(8,8,1)] +void StandardError(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + float variance = g_VarianceInR[id.xy].r; + float error = sqrt(variance / g_SampleCountInW[id.xy].w); + g_TextureOut[id.xy] = float4(error, error, error, 1.f); +} + +Texture2D g_StandardErrorInR; +Texture2D g_MeanInR; +float g_StandardErrorThreshold; +#pragma kernel StandardErrorThreshold +[numthreads(8,8,1)] +void StandardErrorThreshold(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + float mean = g_MeanInR[id.xy].r; + float standardError = g_StandardErrorInR[id.xy].r; + bool shouldStop = standardError < mean * g_StandardErrorThreshold || mean < 0.000001f; + g_TextureOut[id.xy] = shouldStop ? float4(0.0f, 0.0f, 0.0f, 1.f) : float4(1.0f, 1.0f, 1.0f, 1.f); +} + +RWStructuredBuffer g_OutputBuffer; +int g_X; +int g_Y; +#pragma kernel GetValue +[numthreads(8,8,1)] +void GetValue(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + if (id.x == uint(g_X) && id.y == uint(g_Y)) + { + float4 color = g_SourceTexture[id.xy]; + g_OutputBuffer[0] = color.r; + g_OutputBuffer[1] = color.g; + g_OutputBuffer[2] = color.b; + g_OutputBuffer[3] = color.a; + } +} + +#pragma kernel NormalizeByAlpha +[numthreads(8,8,1)] +void NormalizeByAlpha(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_TextureWidth) || id.y >= uint(g_TextureHeight)) + return; + const float4 color = g_TextureInOut[id.xy]; + const float sampleCount = max(color.w, 1.0f); + g_TextureInOut[id.xy] = float4(color.xyzw * (1.f / sampleCount)); +} + +StructuredBuffer g_InputBufferSelector; // 0 or 1 +int g_InputBufferLength; +StructuredBuffer g_InputBuffer0; +StructuredBuffer g_InputBuffer1; +RWStructuredBuffer g_SelectionOutput; +#pragma kernel SelectGraphicsBuffer +[numthreads(16,1,1)] +void SelectGraphicsBuffer(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_InputBufferLength) || id.y >= 1u || id.z >= 1u) + return; + g_SelectionOutput[id.x] = g_InputBufferSelector[0] == 0u ? g_InputBuffer0[id.x] : g_InputBuffer1[id.x]; +} + +RWTexture2D g_DestinationTexture; +int g_SourceX; +int g_SourceY; +int g_SourceWidth; +int g_SourceHeight; +int g_DestinationX; +int g_DestinationY; +#pragma kernel CopyTextureAdditive +[numthreads(8,8,1)] +void CopyTextureAdditive(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_SourceWidth) || id.y >= uint(g_SourceHeight)) + return; + uint2 src = id.xy + uint2(uint(g_SourceX), uint(g_SourceY)); + uint2 dst = id.xy + uint2(uint(g_DestinationX), uint(g_DestinationY)); + g_DestinationTexture[dst] = g_DestinationTexture[dst] + g_SourceTexture[src]; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute.meta new file mode 100644 index 00000000000..ee5066f54e2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ComputeHelpers.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2ba9c897a5bfc8c4e8faefab864e2a59 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute new file mode 100644 index 00000000000..38f8290fe5c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute @@ -0,0 +1,118 @@ +#define UNIFIED_RT_BACKEND_COMPUTE +#define UNIFIED_RT_GROUP_SIZE_X 16 +#define UNIFIED_RT_GROUP_SIZE_Y 8 +#include "ExpansionHelpers.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl" +// Compact an instance GBuffer to a buffer of non-empty texel indices. +// The output buffer has non-empty texels indices written into it. The number of texels written is stored in g_CompactedBufferLength. +// g_CompactedBufferLength must be initialized to 0 before calling this kernel. +#pragma kernel CompactGBuffer +[numthreads(64,1,1)] +void CompactGBuffer(uint3 id : SV_DispatchThreadID) +{ + CompactGBufferInternal(id.x); +} + +uint DivUp(uint x, uint y) +{ + return uint((x + (int)y - 1) / (int)y); +} + +// Populate the indirect dispatch buffer for the accumulation shader. Dimensions are in threads. +int g_ExpandedTexelSampleWidth; +int g_ThreadGroupSizeX; +RWStructuredBuffer g_AccumulationDispatchBuffer; +#pragma kernel PopulateAccumulationDispatch +[numthreads(8,1,1)] +void PopulateAccumulationDispatch(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= 1) + return; + const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0]; + g_AccumulationDispatchBuffer[0] = dimX; + g_AccumulationDispatchBuffer[1] = 1; + g_AccumulationDispatchBuffer[2] = 1; +} + +// Populate the indirect dispatch buffer for the clear shader. +RWStructuredBuffer g_ClearDispatchBuffer; +#pragma kernel PopulateClearDispatch +[numthreads(8,1,1)] +void PopulateClearDispatch(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= 1) + return; + const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0]; + g_ClearDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX)); + g_ClearDispatchBuffer[1] = 1; + g_ClearDispatchBuffer[2] = 1; +} + +// Populate the indirect dispatch buffer for the copy shader. +RWStructuredBuffer g_CopyDispatchBuffer; +#pragma kernel PopulateCopyDispatch +[numthreads(8,1,1)] +void PopulateCopyDispatch(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= 1) + return; + const uint dimX = g_CompactedGBufferLength[0]; + g_CopyDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX)); + g_CopyDispatchBuffer[1] = 1; + g_CopyDispatchBuffer[2] = 1; +} + +// Populate the indirect dispatch buffer for the reduce shader. +RWStructuredBuffer g_ReduceDispatchBuffer; +#pragma kernel PopulateReduceDispatch +[numthreads(8,1,1)] +void PopulateReduceDispatch(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= 1) + return; + const uint dimX = uint(g_ExpandedTexelSampleWidth) * g_CompactedGBufferLength[0]; + g_ReduceDispatchBuffer[0] = DivUp(dimX, uint(g_ThreadGroupSizeX)); + g_ReduceDispatchBuffer[1] = 1; + g_ReduceDispatchBuffer[2] = 1; +} + +StructuredBuffer g_SourceBuffer; +RWTexture2D g_DestinationTexture; +int g_SourceStride; +int g_DestinationX; +int g_DestinationY; +#pragma kernel AdditivelyCopyCompactedTo2D +[numthreads(64,1,1)] +void AdditivelyCopyCompactedTo2D(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_CompactedGBufferLength[0])) + return; + uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * uint(g_InstanceWidth); + uint linearTexelIndex = g_CompactedGBuffer[id.x] + linearChunkOffset; + uint2 texelIndex = uint2(linearTexelIndex % uint(g_InstanceWidth), linearTexelIndex / uint(g_InstanceWidth)) + uint2(uint(g_DestinationX), uint(g_DestinationY)); + g_DestinationTexture[texelIndex] += g_SourceBuffer[id.x*uint(g_SourceStride)]; +} + +// Sums the leftmost value of each group to the left +// The input buffer must be a multiple of g_BinaryGroupSize +RWStructuredBuffer g_Float4Buffer; +#pragma kernel BinaryGroupSumLeft +int g_BinaryBufferSize; +int g_BinaryGroupSize; +[numthreads(64,1,1)] +void BinaryGroupSumLeft(uint3 id : SV_DispatchThreadID) +{ + if (id.x % (2 * uint(g_BinaryGroupSize)) != 0 || id.x + uint(g_BinaryGroupSize) >= uint(g_BinaryBufferSize)) + return; + g_Float4Buffer[id.x] += g_Float4Buffer[id.x + g_BinaryGroupSize]; +} + +int g_Float4BufferLength; +#pragma kernel ClearFloat4Buffer +[numthreads(64,1,1)] +void ClearFloat4Buffer(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= uint(g_Float4BufferLength)) + return; + g_Float4Buffer[id.x] = 0.0f; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute.meta new file mode 100644 index 00000000000..e021cb38e3b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 200e63f12ebe79f4aa306481ce8ce4a4 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl new file mode 100644 index 00000000000..a5f66299534 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl @@ -0,0 +1,32 @@ +#ifndef _PATHTRACING_EXPANSIONHELPERS_HLSL_ +#define _PATHTRACING_EXPANSIONHELPERS_HLSL_ + +#include "PathTracingCommon.hlsl" + +RWStructuredBuffer g_CompactedGBuffer; +RWStructuredBuffer g_CompactedGBufferLength; // This will contain the number of texels written. +int g_ChunkOffsetX; +int g_ChunkOffsetY; +int g_ChunkSize; +int g_InstanceWidth; +Texture2D g_UvFallback; + +void CompactGBufferInternal(uint index) +{ + // The dispatch domain is [0; g_ChunkSize-1] in X + if (index >= (uint)g_ChunkSize) + return; + + const uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * g_InstanceWidth; + const uint linearDispatch = linearChunkOffset + index; + const uint2 instanceTexelPos = uint2(linearDispatch % g_InstanceWidth, linearDispatch / g_InstanceWidth); + + if (g_UvFallback[instanceTexelPos].x < 0) + return; + + uint destinationIndex = 0; + InterlockedAdd(g_CompactedGBufferLength[0], 1, destinationIndex); + g_CompactedGBuffer[destinationIndex] = index; +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl.meta new file mode 100644 index 00000000000..321edcca378 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ExpansionHelpers.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e287e19aac082a84e9ede659dfde55ee +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl new file mode 100644 index 00000000000..ae7c40d96e1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl @@ -0,0 +1,1109 @@ +#ifndef _PATHTRACING_LIGHTSAMPLING_HLSL_ +#define _PATHTRACING_LIGHTSAMPLING_HLSL_ + +#include "PathTracingCommon.hlsl" +#include "PathTracingMaterials.hlsl" +#include "PathTracingLightGrid.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl" +#include "PathTracingSkySampling.hlsl" + +#define SOLID_ANGLE_SAMPLING +#define RESAMPLED_IMPORTANCE_SAMPLING +#define TRANSMISSION_IN_SHADOW_RAYS +#define USE_DISTANCE_ATTENUATION_LUT + +#ifndef QRNG_METHOD_GLOBAL_SOBOL_BLUE_NOISE +// Unless we use global sobol, stratification improves light selection +#define STRATIFIED_LIGHT_PICKING +#endif +TextureCube g_EnvTex; +SamplerState sampler_g_EnvTex; +float g_EnvIntensityMultiplier; + +int g_LightPickingMethod; +StructuredBuffer g_LightList; +StructuredBuffer g_LightFalloff; +StructuredBuffer g_LightFalloffLUTRange; +uint g_LightFalloffLUTLength; +uint g_NumLights; +uint g_NumEmissiveMeshes; + +// Light cookies +Texture2DArray g_CookieAtlas; +TextureCubeArray g_CubemapAtlas; +SamplerState sampler_g_CookieAtlas; +SamplerState sampler_g_CubemapAtlas; + +// Light / reservoir grid +StructuredBuffer g_LightGridCellsData; +StructuredBuffer g_LightGrid; + +// Exposure texture - 1x1 RG16F (r: exposure mult, g: exposure EV100) +TEXTURE2D(_ExposureTexture); +int g_PreExpose; +float g_IndirectScale; +float g_ExposureScale; +int g_MaxIntensity; + + +struct LightShapeSample +{ + float3 lightVector; + float3 L; + float weight; + float distanceToLight; + float2 uv; + int materialIndex; +}; + +struct LightSample +{ + uint lightType; + float3 radiance; + float3 direction; + float risSourcePdf; +}; + +// Copied from com.unity.render-pipelines.high-definition\Runtime\ShaderLibrary\ShaderVariables.hlsl +// URP-only projects will not have this file, so for now copy-pasting here. +float GetCurrentExposureMultiplier() +{ + if (g_PreExpose) + { + #ifdef READ_EXPOSURE_FROM_TEXTURE + // g_ExposureScale is a scale used to perform range compression to avoid saturation of the content of the probes. It is 1.0 if we are not rendering probes. + return LOAD_TEXTURE2D(_ExposureTexture, int2(0, 0)).x * g_ExposureScale; + #else + return g_ExposureScale; + #endif + } + else + { + return 1.0; + } +} + +float3 ClampRadiance(float3 value) +{ + float intensity = Luminance(value) * GetCurrentExposureMultiplier(); + return intensity > g_MaxIntensity ? value * g_MaxIntensity / intensity : value; +} + +bool CastShadowRay(UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + float3 origin, + float3 direction, + float maxDistance, + uint rayMask, + out float3 attenuation) +{ + UnifiedRT::Ray shadowRay; + shadowRay.origin = origin; + shadowRay.direction = direction; + shadowRay.tMin = 0.0; + shadowRay.tMax = maxDistance; + + #ifdef TRANSMISSION_IN_SHADOW_RAYS + uint rayFlags = 0; + #else + uint rayFlags = kRayFlagForceOpaque; + #endif + + return !TraceShadowRay(dispatchInfo, accelStruct, rayMask, shadowRay, rayFlags, attenuation); +} + +float3 CalculateJitteredLightVec(float shadowRadius, float2 rnd, float3 lightVec) +{ + float3x3 lightBasis = OrthoBasisFromVector(normalize(lightVec)); + float2 diskSample = MapSquareToDisk(rnd); + float3 jitterOffset = float3(shadowRadius * diskSample, 0.0f); + jitterOffset = mul(jitterOffset, lightBasis); + return normalize(lightVec + jitterOffset); +} + +bool CastJitteredShadowRay( + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + float shadowRadius, + float3 lightVector, // Vector from ray `origin` to light if light is not a directional light. Otherwise, it is a unit vector pointing toward the directional light. + float3 origin, + float3 direction, + float distance, + uint shadowRayMask, + float2 uSample, + inout uint dimsUsed, + out float3 attenuation) +{ + if (shadowRadius > 0.0f) + { + dimsUsed += 2; + // jitter the light direction within the shadow radius + direction = CalculateJitteredLightVec(shadowRadius, uSample, lightVector); + } + + return CastShadowRay(dispatchInfo, accelStruct, origin, direction, distance, shadowRayMask, attenuation); +} + +struct SphQuad +{ + float3 o, x, y, z; + float z0, z0sq; + float x0, y0, y0sq; + float x1, y1, y1sq; + float b0, b1, b0sq, k; + float S; +}; + +bool SampleRectangularLight(inout PathTracingSampler rngState, uint dimsOffset, out uint dimsUsed, float3 P, PTLight light, inout LightShapeSample lightSample) +{ + float u = rngState.GetFloatSample(dimsOffset); + float v = rngState.GetFloatSample(dimsOffset+1); + dimsUsed = 2; + +#ifndef SOLID_ANGLE_SAMPLING + // Area sampling + u -= 0.5f; + v -= 0.5f; + + float3 position = light.position + u * light.width * light.right + v * light.height * light.up; + lightSample.lightVector = position - P; + lightSample.distanceToLight = length(lightSample.lightVector); + lightSample.L = lightSample.lightVector * rcp(lightSample.distanceToLight); + lightSample.weight = 0; + lightSample.uv = 0; + lightSample.materialIndex = -1; + + float cosTheta = -dot(lightSample.L, light.forward); + if (cosTheta < 0.001) + return false; + + float d = lightSample.distanceToLight; + float lightArea = light.width * light.height; + lightSample.weight = lightArea * cosTheta / (d * d); + + return true; + +#else + // Solid angle sampling + SphQuad squad; + { + // compute local reference system ’R’ + squad.o = P; + squad.x = light.right; + squad.y = light.up; + squad.z = cross(squad.x, squad.y); + + // Adjust the position of the light to be at the center of the rectangle + light.position = light.position - 0.5 * light.width * light.right; + light.position = light.position - 0.5 * light.height * light.up; + + // compute rectangle coords in local reference system + float3 d = light.position - P; + squad.z0 = dot(d, squad.z); + + // flip ’z’ to make it point against ’Q’ + if (squad.z0 > 0) + { + squad.z *= -1; + squad.z0 *= -1; + } + + float exl = light.width; + float eyl = light.height; + squad.z0sq = squad.z0 * squad.z0; + squad.x0 = dot(d, squad.x); + squad.y0 = dot(d, squad.y); + squad.x1 = squad.x0 + exl; + squad.y1 = squad.y0 + eyl; + squad.y0sq = squad.y0 * squad.y0; + squad.y1sq = squad.y1 * squad.y1; + + // create vectors to four vertices + float3 v00 = float3 (squad.x0, squad.y0, squad.z0); + float3 v01 = float3 (squad.x0, squad.y1, squad.z0); + float3 v10 = float3 (squad.x1, squad.y0, squad.z0); + float3 v11 = float3 (squad.x1, squad.y1, squad.z0); + + // compute normals to edges + float3 n0 = normalize(cross(v00, v10)); + float3 n1 = normalize(cross(v10, v11)); + float3 n2 = normalize(cross(v11, v01)); + float3 n3 = normalize(cross(v01, v00)); + + // compute internal angles (gamma_i) + float g0 = acos(-dot(n0, n1)); + float g1 = acos(-dot(n1, n2)); + float g2 = acos(-dot(n2, n3)); + float g3 = acos(-dot(n3, n0)); + + // compute predefined constants + squad.b0 = n0.z; + squad.b1 = n2.z; + squad.b0sq = squad.b0 * squad.b0; + squad.k = 2 * PI - g2 - g3; + + // compute solid angle from internal angles + squad.S = g0 + g1 - squad.k; + } + + // Generate sample + lightSample = (LightShapeSample)0; + + if (squad.S < 0.00001 || isnan(squad.S)) + return false; + + // 1. compute ’cu’ + float au = u * squad.S + squad.k; + float fu = (cos(au) * squad.b0 - squad.b1) / sin(au); + float cu = 1 / sqrt(fu * fu + squad.b0sq);// *(fu > 0 ? +1 : -1); + cu = (fu > 0.0f) ? cu : -cu; + cu = clamp(cu, -1, 1); // avoid NaNs + + // 2. compute ’xu’ + float xu = -(cu * squad.z0) / sqrt(1 - cu * cu); + xu = clamp(xu, squad.x0, squad.x1); // avoid Infs + + // 3. compute ’yv’ + float d = sqrt(xu * xu + squad.z0sq); + float h0 = squad.y0 / sqrt(d * d + squad.y0sq); + float h1 = squad.y1 / sqrt(d * d + squad.y1sq); + float hv = h0 + v * (h1 - h0); + float hv2 = hv * hv; + float eps = 0.0001; + float yv = (hv2 < 1.0 - eps) ? (hv * d) / sqrt(1.0 - hv2) : squad.y1; + + // 4. transform (xu,yv,z0) to world coords + float3 position = (squad.o + xu * squad.x + yv * squad.y + squad.z0 * squad.z); + + lightSample.lightVector = position - P; + lightSample.distanceToLight = length(lightSample.lightVector); + lightSample.L = lightSample.lightVector * rcp(lightSample.distanceToLight); + lightSample.weight = squad.S; + + // Cookie texture coordinates + lightSample.uv = float2((xu - squad.x0) / (squad.x1 - squad.x0) - 0.5, (yv - squad.y0) / (squad.y1 - squad.y0) - 0.5); + lightSample.uv.y = 1.0 - lightSample.uv.y; + lightSample.materialIndex = -1; + + float cosTheta = -dot(lightSample.L, light.forward); + if (cosTheta < eps) + return false; + + return true; +#endif +} + +bool SampleDiscLight(inout PathTracingSampler rngState, uint dimsOffset, out uint dimsUsed, float3 P, PTLight light, inout LightShapeSample lightSample) +{ + float u = rngState.GetFloatSample(dimsOffset); + float v = rngState.GetFloatSample(dimsOffset + 1); + dimsUsed = 2; + + float2 coord = SampleDiskUniform(u, v); + const float radius = light.width; + float3 posOnDisk = radius * (coord.x * light.right + coord.y * light.up); + float3 position = light.position + posOnDisk; + + lightSample.lightVector = position - P; + lightSample.distanceToLight = length(lightSample.lightVector); + lightSample.L = lightSample.lightVector * rcp(lightSample.distanceToLight); + lightSample.weight = 0; + + // Cookie texture coordinates + float lightDiameter = 2 * light.width; + float centerU = dot(posOnDisk, light.right) / (lightDiameter * Length2(light.right)); + float centerV = dot(posOnDisk, light.up) / (lightDiameter * Length2(light.up)); + lightSample.uv = float2(centerU - 0.5, centerV - 0.5); + lightSample.uv.x = 1.0 - lightSample.uv.x; + lightSample.materialIndex = -1; + + float cosTheta = -dot(lightSample.L, light.forward); + if (cosTheta < 0.001) + return false; + + float d = lightSample.distanceToLight; + float lightArea = PI * light.width * light.width; + lightSample.weight = lightArea * cosTheta / (d * d); + return true; +} + +// A Low-Distortion Map Between Triangle and Square, by Eric Heitz, 2019 +// Preserves blue-noise quality of input samples better than sqrt parameterization. +float2 MapUnitSquareToUnitTriangle(float2 unitSquareCoords) +{ + if (unitSquareCoords.y > unitSquareCoords.x) + { + unitSquareCoords.x *= 0.5f; + unitSquareCoords.y -= unitSquareCoords.x; + } + else + { + unitSquareCoords.y *= 0.5f; + unitSquareCoords.x -= unitSquareCoords.y; + } + return unitSquareCoords; +} + +bool SampleEmissiveMesh(StructuredBuffer instanceList, inout PathTracingSampler rngState, uint dimsOffset, out uint dimsUsed, float3 P, PTLight light, inout LightShapeSample lightSample) +{ + float r1 = rngState.GetFloatSample(dimsOffset); + float r2 = rngState.GetFloatSample(dimsOffset + 1); + float r3 = rngState.GetFloatSample(dimsOffset + 2); + dimsUsed = 3; + + // random point in unit triangle + float2 samplePoint = MapUnitSquareToUnitTriangle(float2(r1, r2)); + + int instanceIndex = light.height; + int numPrimitives = light.attenuation.x; + int primitiveIndex = (r3 * numPrimitives) % numPrimitives; + + // fetch the triangle vertices from the geometry pool + + int geometryIndex = instanceList[instanceIndex].geometryIndex; + GeoPoolMeshChunk meshInfo = g_MeshList[geometryIndex]; + uint3 triangleVertexIndices = UnifiedRT::Internal::FetchTriangleIndices(meshInfo, primitiveIndex); + + GeoPoolVertex v1, v2, v3; + v1 = UnifiedRT::Internal::FetchVertex(meshInfo, triangleVertexIndices.x); + v2 = UnifiedRT::Internal::FetchVertex(meshInfo, triangleVertexIndices.y); + v3 = UnifiedRT::Internal::FetchVertex(meshInfo, triangleVertexIndices.z); + + UnifiedRT::InstanceData instanceInfo = UnifiedRT::GetInstance(instanceIndex); + v1.pos = mul(instanceInfo.localToWorld, float4(v1.pos, 1)).xyz; + v2.pos = mul(instanceInfo.localToWorld, float4(v2.pos, 1)).xyz; + v3.pos = mul(instanceInfo.localToWorld, float4(v3.pos, 1)).xyz; + + float3 geometricNormal = cross(v2.pos - v1.pos, v3.pos - v1.pos); + + // compute the random position + float3 position = samplePoint.x * v1.pos + samplePoint.y * v2.pos + (1 - samplePoint.x - samplePoint.y) * v3.pos; + + lightSample.lightVector = position - P; + lightSample.distanceToLight = length(lightSample.lightVector); + lightSample.L = lightSample.lightVector * rcp(lightSample.distanceToLight); + lightSample.distanceToLight *= 0.99; // avoid self intersection with mesh light geometry + lightSample.weight = 0; + + // Interpolate the texture coordinates if needed + int materialIndex = instanceInfo.userMaterialID; + lightSample.materialIndex = materialIndex; + PTMaterial matInfo = g_MaterialList[materialIndex]; + + if (matInfo.emissionTextureIndex != -1) + { + if (matInfo.albedoAndEmissionUVChannel == 1) + lightSample.uv = samplePoint.x * v1.uv1.xy + samplePoint.y * v2.uv1.xy + (1 - samplePoint.x - samplePoint.y) * v3.uv1.xy; + else + lightSample.uv = samplePoint.x * v1.uv0.xy + samplePoint.y * v2.uv0.xy + (1 - samplePoint.x - samplePoint.y) * v3.uv0.xy; + } + else + lightSample.uv = 0; + + float cosTheta = -dot(lightSample.L, normalize(geometricNormal)); + const bool doubleSided = matInfo.flags & 2; + if (cosTheta < 0.001 && !doubleSided) + return false; + + float d = lightSample.distanceToLight; + + float lightArea = 0.5 * length(geometricNormal); + + lightSample.weight = lightArea * abs(cosTheta) / (d * d); + lightSample.weight *= numPrimitives; //uniform light selection pdf + + return true; +} + +float2 PunctualLightCookieUVs(float3 L, PTLight light) +{ + // Handles spot, box and directional lights. + light.right *= 2.0f / light.width; + light.up *= 2.0f / light.height; + + float3x3 lightToWorld = float3x3(light.right, light.up, light.forward); + float3 positionLS = mul(-L, transpose(lightToWorld)); + + float perspectiveZ = (light.type != BOX_LIGHT && light.type != DIRECTIONAL_LIGHT) ? positionLS.z : 1.0f; + float2 positionCS = positionLS.xy / perspectiveZ; + float2 positionNDC = positionCS * 0.5f + 0.5f; + return positionNDC; +} + +bool SamplePunctualLight(float3 P, PTLight light, out LightShapeSample lightSample) +{ + lightSample.lightVector = light.type == DIRECTIONAL_LIGHT ? -light.forward : light.position - P; + lightSample.distanceToLight = light.type == DIRECTIONAL_LIGHT ? K_T_MAX : length(lightSample.lightVector); + lightSample.L = normalize(lightSample.lightVector); + lightSample.weight = 1.0f; + lightSample.materialIndex = -1; + + // Light cookie UVs (note: for pointlights we use the light vector to access the cookie) + lightSample.uv = 0.0f; + if (light.cookieIndex >= 0 && light.type != POINT_LIGHT) + { + lightSample.uv = PunctualLightCookieUVs(light.position - P, light); + } + + return true; +} + +bool SampleEnvironmentLight(inout PathTracingSampler rngState, uint dimsOffset, out uint dimsUsed, float3 P, PTLight light, out LightShapeSample lightSample) +{ + + lightSample = (LightShapeSample)0; + float r1 = rngState.GetFloatSample(dimsOffset); + float r2 = rngState.GetFloatSample(dimsOffset+1); + dimsUsed = 2; + +#ifdef UNIFORM_ENVSAMPLING + // Sample the environment with a random direction. Should only be used for reference / ground truth. + lightSample.lightVector = SampleSphereUniform(r1, r2); + lightSample.weight = 4 * PI; +#else + float normalizationFactor = GetSkyPDFNormalizationFactor(); + + // A normalization factor of zero means that the environment cubemap was zero, and in this case + // its PDF isn't well-defined. It is safe to bail in this case, because the environment wouldn't + // contribute anything anyway. + if (normalizationFactor == 0.0f) + return false; + + // Sample the environment CDF + float2 u = SampleSky(r1, r2); + lightSample.lightVector = MapUVToSkyDirection(u); + + float3 envValue = g_EnvTex.SampleLevel(sampler_g_EnvTex, lightSample.lightVector, 0).xyz; + if (all(envValue == 0.0)) + return false; + + float skyPdf = GetSkyPDFFromValue(envValue, normalizationFactor); + lightSample.weight = rcp(skyPdf); +#endif + + lightSample.L = normalize(lightSample.lightVector); + lightSample.distanceToLight = K_T_MAX; + lightSample.uv = 0; + lightSample.materialIndex = -1; + + return true; +} + +bool SampleLightShape(StructuredBuffer instanceList, inout PathTracingSampler rngState, uint dimsOffset, out uint dimsUsed, float3 P, PTLight light, out LightShapeSample lightSample) +{ + dimsUsed = 0; + lightSample = (LightShapeSample)0; + bool result = false; + if (light.type == RECTANGULAR_LIGHT) + { + result = SampleRectangularLight(rngState, dimsOffset, dimsUsed, P, light, lightSample); + } + else if (light.type == DISC_LIGHT) + { + result = SampleDiscLight(rngState, dimsOffset, dimsUsed, P, light, lightSample); + } + else if (light.type == EMISSIVE_MESH) + { + result = SampleEmissiveMesh(instanceList, rngState, dimsOffset, dimsUsed, P, light, lightSample); + } + else if (light.type == ENVIRONMENT_LIGHT) + { + result = SampleEnvironmentLight(rngState, dimsOffset, dimsUsed, P, light, lightSample); + } + else + { + result = SamplePunctualLight(P, light, lightSample); + } + return result && lightSample.distanceToLight != 0.0f; +} + +// Find the smallest `i` between 0 and `cdfLength-1` such that `cdf[i] < rand < cdf[i+1]`. +// `cdf` must be normalized and `rand` must be between 0 and 1. +uint BinarySearchCdf(in StructuredBuffer cdf, uint cdfLength, float rand) +{ + const uint maxSteps = log2((float)cdfLength) + 1; + + uint leftIdx = 0; + uint rightIdx = cdfLength - 1; + for (uint i = 0; i < maxSteps; ++i) + { + const uint candidateIdx = (leftIdx + rightIdx) / 2; + const float higherVal = cdf[candidateIdx]; + if (higherVal < rand) + { + leftIdx = candidateIdx + 1; + continue; + } + + const float lowerVal = candidateIdx == 0 ? 0.0f : cdf[candidateIdx - 1]; + if (rand < lowerVal) + { + rightIdx = candidateIdx - 1; + continue; + } + + return candidateIdx; + } + + return UINT_MAX; // Unexpected. +} + +void PickLightUniformly(float randomSample, out uint lightIndex, out float probabilityMass) +{ + lightIndex = g_NumLights * randomSample; + // Guard in case the RNG returns 1 or higher + lightIndex = lightIndex % g_NumLights; + probabilityMass = 1.0f / g_NumLights; +} + +void PickLightFromGrid(float randomSample, float3 pos, out uint lightIndex, out float probabilityMass) +{ + uint cellIndex = GetCellIndexFromPosition(pos); + uint firstReservoir = g_LightGrid[cellIndex].x; + uint numReservoirs = g_LightGrid[cellIndex].y; + uint reservoirIndex = min(numReservoirs * randomSample, numReservoirs - 1); // Guard in case the RNG returns 1 or higher + + ThinReservoir reservoir = g_LightGridCellsData[firstReservoir + reservoirIndex]; + + lightIndex = reservoir.lightIndex; + probabilityMass = reservoir.weight / numReservoirs; // TODO: move the division at build time +} + +uint FindEmissiveMeshLightIndex(int instanceID) +{ + for (uint i = 0; i < g_NumEmissiveMeshes; i++) + if (GetMeshLightInstanceID(g_LightList[i]) == instanceID) + return i; + return UINT_MAX; // Unexpected. +} + +float SampleFalloff(int falloffIndex, float distance, float range) +{ + const float LUTRange = g_LightFalloffLUTRange[falloffIndex]; + if (distance > LUTRange) + return 0.0f; // The distance is outside the range of the falloff LUT. + const float normalizedSamplePosition = distance / LUTRange; + const int sampleCount = g_LightFalloffLUTLength; + const int LUTOffset = falloffIndex * sampleCount; + const float index = normalizedSamplePosition * float(sampleCount); + + // compute the index pair + const int loIndex = min(int(index), int(sampleCount - 1)); + const int hiIndex = min(int(index) + 1, int(sampleCount - 1)); + const float hiFraction = (index - float(loIndex)); + + const float sampleLo = g_LightFalloff[LUTOffset+loIndex]; + const float sampleHi = g_LightFalloff[LUTOffset+hiIndex]; + + // do the lerp + return (1.0f - hiFraction) * sampleLo + hiFraction * sampleHi; +} + +// TODO: Add support for angular falloff LUT, that is used in progressive lightmapper. https://jira.unity3d.com/browse/LIGHT-1770 +// distances = {d, d^2, 1/d, d_proj}, where d_proj = dot(lightToSample, lightData.forward). +float PunctualLightAngleAttenuation(float4 distances, float rangeAttenuationScale, float rangeAttenuationBias, float lightAngleScale, float lightAngleOffset) +{ + float distRcp = distances.z; + float distProj = distances.w; + float cosFwd = distProj * distRcp; + float attenuation = AngleAttenuation(cosFwd, lightAngleScale, lightAngleOffset); + return Sq(attenuation); +} + +float GetPunctualAttenuation(PTLight light, LightShapeSample lightSample) +{ + float x = abs(dot(lightSample.lightVector, 2 * light.right / light.width)); + float y = abs(dot(lightSample.lightVector, 2 * light.up / light.height)); + float z = abs(dot(lightSample.lightVector, light.forward)); + + if (light.type == BOX_LIGHT) + { + // box attenuation + return ((z > 0) && (x < 1.0 && y < 1.0)) ? 1.0 : 0.0; + } + + // Punctual attenuation + float attenuation = 1.0f; + const float d = lightSample.distanceToLight; + float4 distances = float4(d, Sq(d), rcp(d), -d * dot(lightSample.L, light.forward)); + +#ifdef USE_DISTANCE_ATTENUATION_LUT + if (light.falloffIndex >= 0) + attenuation *= SampleFalloff(light.falloffIndex, d, light.range); + + attenuation *= PunctualLightAngleAttenuation(distances, light.attenuation.x, light.attenuation.y, light.attenuation.z, light.attenuation.w); +#else + attenuation *= PunctualLightAttenuation(distances, light.attenuation.x, light.attenuation.y, light.attenuation.z, light.attenuation.w); +#endif + + return attenuation; +} + +float3 PointLightCookie(PTLight light, LightShapeSample lightSample) +{ + if (light.cookieIndex >= 0) + { + float3x3 lightToWorld = float3x3(light.right, light.up, light.forward); + float3 L = mul(-lightSample.L, transpose(lightToWorld)); + return g_CubemapAtlas.SampleLevel(sampler_g_CubemapAtlas, float4(L, light.cookieIndex), 0).xyz; + } + else + return 1.0f; +} + +float3 LightCookie(PTLight light, LightShapeSample lightSample) +{ + if (light.cookieIndex >= 0) + { + return g_CookieAtlas.SampleLevel(sampler_g_CookieAtlas, float3(lightSample.uv, light.cookieIndex), 0).xyz; + } + else + return 1.0f; +} + +float3 AreaCookieAttenuation(PTLight light, LightShapeSample lightSample) +{ + if (light.cookieIndex >= 0) + { + return g_CookieAtlas.SampleLevel(sampler_g_CookieAtlas, float3(lightSample.uv, light.cookieIndex), 0).xyz; + } + else + return 1.0f; +} + +float3 PunctualCookieAttenuation(PTLight light, LightShapeSample lightSample) +{ + if (light.type == SPOT_LIGHT || light.type == BOX_LIGHT) + return LightCookie(light, lightSample); + else if (light.type == POINT_LIGHT) + return PointLightCookie(light, lightSample); + else + return 1.0; +} + +float3 GetPunctualEmission(PTLight light, LightShapeSample lightSample) +{ + float3 emission = light.intensity * GetPunctualAttenuation(light, lightSample); + float3 cookieAttenuation = PunctualCookieAttenuation(light, lightSample); + return emission * cookieAttenuation; +} + +float3 GetDirectionalEmission(PTLight light, LightShapeSample lightSample) +{ + return light.intensity * LightCookie(light, lightSample); +} + +float3 GetRectangularLightEmission(PTLight light, LightShapeSample lightSample) +{ + if (light.range < lightSample.distanceToLight) + return 0.f; + float3 emission = light.intensity; + + float3 cookieAttenuation = AreaCookieAttenuation(light, lightSample); + return emission * cookieAttenuation; +} + +float3 GetDiscLightEmission(PTLight light, LightShapeSample lightSample) +{ + if (light.range < lightSample.distanceToLight) + return float3(0.f, 0.f, 0.f); + else + { + float3 emission = light.intensity; + float3 cookieAttenuation = AreaCookieAttenuation(light, lightSample); + return emission * cookieAttenuation; + } +} + +float3 GetEmissiveMeshEmission(PTLight light, LightShapeSample lightSample) +{ + PTMaterial matInfo = g_MaterialList[lightSample.materialIndex]; + + float3 emission = 0; + if (matInfo.emissionTextureIndex != -1) + { + emission = SampleAtlas(g_EmissionTextures, sampler_g_EmissionTextures, matInfo.emissionTextureIndex, lightSample.uv, matInfo.emissionScale, matInfo.emissionOffset, false).rgb; + } + else + { + emission = matInfo.emissionColor; + } + + return emission; +} + +float3 GetEnvironmentLightEmission(float3 direction) +{ + // Sample the environment and apply intensity multiplier + return g_EnvIntensityMultiplier * g_EnvTex.SampleLevel(sampler_g_EnvTex, direction, 0).xyz; +} + +bool GetEnvironmentLightEmissionAndDensity(float3 direction, out float3 emission, out float density) +{ + emission = GetEnvironmentLightEmission(direction); + + // In the special case when the environment is zero/black, its importance sampling PDF is undefined. + // This check ensures we don't evaluate the PDF in this case. + if (any(emission != 0.0f)) + { + #ifdef UNIFORM_ENVSAMPLING + density = rcp(4 * PI); + #else + density = GetSkyPDFFromValue(emission); + #endif + return true; + } + else + { + density = 0; + return false; + } +} + +float3 GetEmission(PTLight light, LightShapeSample lightSample) +{ + float3 emission = 0; + if (light.type == DIRECTIONAL_LIGHT) + { + emission = GetDirectionalEmission(light, lightSample); + } + else if (light.type == SPOT_LIGHT || light.type == POINT_LIGHT || light.type == BOX_LIGHT) + { + emission = GetPunctualEmission(light, lightSample); + } + else if (light.type == RECTANGULAR_LIGHT) + { + emission = GetRectangularLightEmission(light, lightSample); + } + else if (light.type == DISC_LIGHT) + { + emission = GetDiscLightEmission(light, lightSample); + } + else if (light.type == EMISSIVE_MESH) + { + emission = GetEmissiveMeshEmission(light, lightSample); + } + else if (light.type == ENVIRONMENT_LIGHT) + { + emission = GetEnvironmentLightEmission(lightSample.L); + } + return emission; +} + +// Emissive surfaces are also sampled explicitely as lights. The following functions compute MIS weight to combine the two sampling strategies. +float EmissiveMISWeightForLightRay(int lightType, float3 lightDirection, float lightPdf, float3 worldNormal) +{ + if (lightType == EMISSIVE_MESH || lightType == ENVIRONMENT_LIGHT) + { + float cosTheta = dot(worldNormal, lightDirection); + float brdfPdf = cosTheta / PI; +#if (EMISSIVE_SAMPLING == LIGHT_SAMPLING) + float misWeight = 1; +#elif (EMISSIVE_SAMPLING == BRDF_SAMPLING) + float misWeight = 0; +#else + float misWeight = PowerHeuristic(lightPdf, brdfPdf); +#endif + return misWeight; + } + return 1.0; +} + +float EmissiveMISWeightForBrdfRay(float lightPdf, float brdfPdf){ + +#if (EMISSIVE_SAMPLING == LIGHT_SAMPLING) + float misWeight = 0; +#elif (EMISSIVE_SAMPLING == BRDF_SAMPLING) + float misWeight = 1; +#else + // brdfPdf > 0 condition is here to be able to disable MIS for LiveGI's primary rays. + // When the primary camera ray directly hits an emissive surface, we should not do MIS at all. + // We did not have a bounce yet and we therefore set iterator.lastScatterProbabilityDensityto (brdfPdf) to 0. + float misWeight = brdfPdf > 0 ? PowerHeuristic(brdfPdf, lightPdf) : 1.0; +#endif + return misWeight; +} + +PTLight FetchLight(int lightIndex) +{ + return g_LightList[lightIndex]; +} + +uint PackLightInfo(PTLight light) +{ + uint bitmask = 0; + bitmask |= light.castsShadows; + bitmask |= (light.type << 1); + return bitmask; +} + +void UnpackLightInfo(uint info, out int castsShadows, out int lightType) +{ + castsShadows = info & 1; + lightType = info >> 1; +} + +struct ReservoirLightSample +{ + uint lightInfo; // packs the "cast shadows" flag and the light type + float3 lightDirection; + float distanceToLight; + float3 radiance; + float shadowRadius; + float pdf; // for MIS +}; + +struct Reservoir +{ + float totalWeights; + ReservoirLightSample bestSample; + + void Update(ReservoirLightSample candidateSample, float candidateRisWeight, float rand01) + { + totalWeights += candidateRisWeight; + + if (rand01 * totalWeights < candidateRisWeight) + bestSample = candidateSample; + } +}; + +uint GetNumLights(float3 origin, uint maxLightEvaluations) +{ + if (g_LightPickingMethod == LIGHT_PICKING_METHOD_RESERVOIR_GRID || g_LightPickingMethod == LIGHT_PICKING_METHOD_LIGHT_GRID) + { + uint cellIndex = GetCellIndexFromPosition(origin); + uint numReservoirs = g_LightGrid[cellIndex].y; + return min(numReservoirs, maxLightEvaluations); + } + + return min(maxLightEvaluations, g_NumLights); +} + +bool SampleLight(PTLight light, float3 shadowRayOrigin, float3 normal, bool isDirect, uint enabledLightsLayerMask, StructuredBuffer instanceList, uint dimsOffset, inout PathTracingSampler rngState, out ReservoirLightSample lightSample) +{ + lightSample = (ReservoirLightSample)0; + + // skip light if it contributes only to indirect illumination + if (isDirect && !light.contributesToDirectLighting) + return false; + + // skip lights based on light layer + if ((light.layerMask & enabledLightsLayerMask) == 0) + return false; + + LightShapeSample lightShapeSample; + + uint dimsUsed = 0; + if (!SampleLightShape(instanceList, rngState, dimsOffset, dimsUsed, shadowRayOrigin, light, lightShapeSample)) + return false; + + float3 emission = GetEmission(light, lightShapeSample); + if (!isDirect) + emission *= light.indirectScale * g_IndirectScale; + + if (dot(normal, lightShapeSample.L) < 0) + return false; + + if (Luminance(emission * lightShapeSample.weight) == 0) + return false; + + lightSample.lightInfo = PackLightInfo(light); + lightSample.lightDirection = lightShapeSample.L; + lightSample.distanceToLight = lightShapeSample.distanceToLight; + lightSample.pdf = rcp(lightShapeSample.weight); + lightSample.radiance = emission; + lightSample.shadowRadius = light.shadowRadius; + + return true; +} + +struct SampleLightsOptions +{ + bool isDirect; + bool receiveShadows; + uint shadowRayMask; + uint lightsRenderingLayerMask; + uint numLightCandidates; +}; + +PTLight PickLight(float pickingSample, float3 receiverOrigin, out float lighPickingPmf) +{ + uint lightIndex; + if (g_LightPickingMethod == LIGHT_PICKING_METHOD_RESERVOIR_GRID || g_LightPickingMethod == LIGHT_PICKING_METHOD_LIGHT_GRID) + PickLightFromGrid(pickingSample, receiverOrigin, lightIndex, lighPickingPmf); + else + PickLightUniformly(pickingSample, lightIndex, lighPickingPmf); + + return FetchLight(lightIndex); +} + +bool SampleLightsRadianceRIS( + UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, + float3 receiverOrigin, float3 receiverNormal, SampleLightsOptions options, inout PathTracingSampler rngState, out LightSample resLightSample) +{ + resLightSample = (LightSample)0; + // Find how many lights we will evaluate + uint numLightCandidates = GetNumLights(receiverOrigin, options.numLightCandidates); + + Reservoir reservoir = (Reservoir) 0; + + for (uint i = 0; i < numLightCandidates; ++i) + { + float pickingSample = rngState.GetFloatSample(RAND_DIM_LIGHT_SELECTION + RAND_SAMPLES_PER_LIGHT * i); + #ifdef STRATIFIED_LIGHT_PICKING + pickingSample = (i + pickingSample) / numLightCandidates; + #endif + + float lighPickingPmf; + PTLight light = PickLight(pickingSample, receiverOrigin, lighPickingPmf); + + uint dimsOffset = RAND_DIM_LIGHT_SELECTION + RAND_SAMPLES_PER_LIGHT * i + 1; + + ReservoirLightSample lightSample; + if (!SampleLight(light, receiverOrigin, receiverNormal, options.isDirect, options.lightsRenderingLayerMask, instanceList, dimsOffset, rngState, lightSample)) + continue; + + lightSample.pdf *= lighPickingPmf; + + float r = rngState.GetFloatSample(RAND_DIM_LIGHT_SELECTION + RAND_SAMPLES_PER_LIGHT * i + 4); + float targetFunc = Luminance(lightSample.radiance); + float risWeight = targetFunc / lightSample.pdf; // w = targetFunc / sourcePdf + reservoir.Update(lightSample, risWeight, r); + } + + float sampleTargetFuncEval = Luminance(reservoir.bestSample.radiance); + if (!(reservoir.totalWeights > 0.0f && sampleTargetFuncEval > 0.0f)) + return false; + + ReservoirLightSample bestSample = reservoir.bestSample; + + int castShadows; + int lightType; + UnpackLightInfo(bestSample.lightInfo, castShadows, lightType); + + // cast a shadow ray + float3 attenuation = 1.0f; + uint dimsUsed = 0; + float2 shadowSample = float2(rngState.GetFloatSample(RAND_DIM_JITTERED_SHADOW_X), rngState.GetFloatSample(RAND_DIM_JITTERED_SHADOW_Y)); + bool isShadowed = castShadows && options.receiveShadows && !CastJitteredShadowRay(dispatchInfo, accelStruct, bestSample.shadowRadius, lightType == DIRECTIONAL_LIGHT ? bestSample.lightDirection : bestSample.lightDirection*bestSample.distanceToLight, receiverOrigin, bestSample.lightDirection, bestSample.distanceToLight, options.shadowRayMask, shadowSample, dimsUsed, attenuation); + if (isShadowed) + return false; + + float unbiasedContributionWeight = (reservoir.totalWeights / float(numLightCandidates)) / sampleTargetFuncEval; + float3 radiance = bestSample.radiance * unbiasedContributionWeight * attenuation; + + resLightSample.radiance = radiance; + resLightSample.direction = bestSample.lightDirection; + resLightSample.risSourcePdf = bestSample.pdf; + resLightSample.lightType = lightType; + + return true; +} + +// Uniform sampling of the light list, used for reference / debuging +bool SampleLightsRadianceMC( + UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, + float3 receiverOrigin, float3 receiverNormal, SampleLightsOptions options, inout PathTracingSampler rngState, out LightSample resLightSample) +{ + resLightSample = (LightSample)0; + // Find how many lights we will evaluate + uint numLightCandidates = GetNumLights(receiverOrigin, 1); + + float pickingSample = rngState.GetFloatSample(RAND_DIM_LIGHT_SELECTION); + float lighPickingPmf; + PTLight light = PickLight(pickingSample, receiverOrigin, lighPickingPmf); + + uint dimsOffset = RAND_DIM_LIGHT_SELECTION + 1; + ReservoirLightSample lightSample; + if (!SampleLight(light, receiverOrigin, receiverNormal, options.isDirect, options.lightsRenderingLayerMask, instanceList, dimsOffset, rngState, lightSample)) + return false; + + lightSample.pdf *= lighPickingPmf; + + float3 attenuation = 1.0f; + uint dimsUsed = 0; + float2 shadowSample = float2(rngState.GetFloatSample(RAND_DIM_JITTERED_SHADOW_X), rngState.GetFloatSample(RAND_DIM_JITTERED_SHADOW_Y)); + bool isShadowed = light.castsShadows && options.receiveShadows && !CastJitteredShadowRay(dispatchInfo, accelStruct, light.shadowRadius, light.type == DIRECTIONAL_LIGHT ? lightSample.lightDirection : lightSample.lightDirection*lightSample.distanceToLight, receiverOrigin, lightSample.lightDirection, lightSample.distanceToLight, options.shadowRayMask, shadowSample, dimsUsed, attenuation); + if (isShadowed) + return false; + + resLightSample.radiance = attenuation * lightSample.radiance / lightSample.pdf; + resLightSample.direction = lightSample.lightDirection; + resLightSample.risSourcePdf = lightSample.pdf; + resLightSample.lightType = light.type; + + return true; +} + +bool SampleLightsRadiance( + UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, + float3 receiverOrigin, float3 receiverNormal, SampleLightsOptions options, inout PathTracingSampler rngState, out LightSample resLightSample) +{ +#ifdef RESAMPLED_IMPORTANCE_SAMPLING + return SampleLightsRadianceRIS(dispatchInfo, accelStruct, instanceList, receiverOrigin, receiverNormal, options, rngState, resLightSample); +#else + return SampleLightsRadianceMC(dispatchInfo, accelStruct, instanceList, receiverOrigin, receiverNormal, options, rngState, resLightSample); +#endif +} + +float3 EvalDirectIllumination( + UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, + bool isDirect, uint shadowRayMask, PTHitGeom hitGeom, MaterialProperties material, uint maxLightEvaluations, inout PathTracingSampler rngState) +{ + float3 irradiance = 0.0f; + float3 shadowRayOrigin = hitGeom.NextRayOrigin(); + + SampleLightsOptions options; + options.isDirect = isDirect; + options.receiveShadows = true; + options.shadowRayMask = shadowRayMask; + options.lightsRenderingLayerMask = hitGeom.renderingLayerMask; + options.numLightCandidates = maxLightEvaluations; + + LightSample lightSample; + if (SampleLightsRadiance(dispatchInfo, accelStruct, instanceList, shadowRayOrigin, hitGeom.worldNormal, options, rngState, lightSample)) + { + float3 eval = EvalDiffuseBrdf(material.baseColor) * ClampedCosine(hitGeom.worldNormal, lightSample.direction) * lightSample.radiance; + eval *= EmissiveMISWeightForLightRay(lightSample.lightType, lightSample.direction, lightSample.risSourcePdf, hitGeom.worldNormal); + irradiance = ClampRadiance(eval); + } + + return irradiance; +} + +// Evaluate direct shadows from a single light given the index of the light. Used for baking. +bool IsLightVisibleFromPoint( + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + StructuredBuffer instanceList, + uint shadowRayMask, + float3 worldPosition, + inout PathTracingSampler rngState, + uint dimsOffset, + out uint dimsUsed, + PTLight light, + bool receiveShadows, + out float3 attenuation) +{ + attenuation = 1.0f; + + LightShapeSample lightSample; + if (!SampleLightShape(instanceList, rngState, dimsOffset, dimsUsed, worldPosition, light, lightSample)) + return false; + + if (light.type != DIRECTIONAL_LIGHT && lightSample.distanceToLight >= light.range) + return false; + if (light.type == SPOT_LIGHT && GetPunctualAttenuation(light, lightSample) < 0.000001f) + return false; + + if (light.castsShadows && receiveShadows) + { + float2 shadowSample = float2(rngState.GetFloatSample(dimsOffset), rngState.GetFloatSample(dimsOffset+1)); + return CastJitteredShadowRay(dispatchInfo, accelStruct, light.shadowRadius, lightSample.lightVector, worldPosition, lightSample.L, lightSample.distanceToLight, shadowRayMask, shadowSample, dimsUsed, attenuation); + } + return true; +} + +#endif // _PATHTRACING_LIGHTSAMPLING_HLSL_ diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl.meta new file mode 100644 index 00000000000..2029e33d373 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightSampling.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f4d1137d7b0a472438ff16f81047ba11 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader new file mode 100644 index 00000000000..6835ae53b5b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader @@ -0,0 +1,72 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_SampleOffset; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; +float g_AOMaxDistance; + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + // setup rng + const uint sampleOffset = g_SampleOffset + localSampleOffset; + PathTracingSampler rngState; + rngState.Init(instanceTexelPos, sampleOffset); + + // now sample occlusion with the gBuffer data + UnifiedRT::Ray ray; + float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff); + float3 direction = CosineSample(float2(rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_X), rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_Y)), worldNormal); + ray.origin = origin; + ray.direction = direction; + ray.tMin = 0; + ray.tMax = g_AOMaxDistance; + rngState.NextBounce(); + + float3 occlusion = 0.f; + // trace through potentially several layers of transmissive materials, determining at each hit whether or not to kill the ray + for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++) + { + UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0); + if (hitResult.IsValid()) + { + UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID); + PTHitGeom geometry = GetHitGeomInfo(instance, hitResult); + geometry.FixNormals(direction); + MaterialProperties material = LoadMaterialProperties(instance, false, geometry); + + // Transmissive material, continue with a probability + if (ShouldTransmitRay(rngState, material)) + { + ray.origin = geometry.NextTransmissionRayOrigin(); + ray.tMax = g_AOMaxDistance - distance(origin, ray.origin); + rngState.NextBounce(); + } + // No transmission, so the ray is occluded + else + { + occlusion = 1.f; + break; + } + } + // Hit nothing, so the ray is not occluded + else + break; + } + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(occlusion, 1.0f); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader.meta new file mode 100644 index 00000000000..5a411f429ad --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapAOIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 1c410815c01ce1d4d82c645c0cd45747 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader new file mode 100644 index 00000000000..41abdec4673 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader @@ -0,0 +1,162 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +// Set to MIS, LIGHT_SAMPLING or BRDF_SAMPLING for debugging. +#define EMISSIVE_SAMPLING MIS + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_AccumulateDirectional; +int g_SampleOffset; +uint g_ReceiveShadows; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; +RWStructuredBuffer g_ExpandedDirectional; + +// Sample every light directly, weight with MIS. +// Assumes 2 sampling strategies, the other being cosine weighted hemisphere sampling. +void EstimateMISWeightedIrradianceUsingDirectSampling( + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + StructuredBuffer instanceList, + float3 origin, + float3 normal, + bool receiveShadows, + inout PathTracingSampler rngState, + inout float4 irradiance, + inout float4 directional) +{ + float3 sampleRadiance = 0.f; + float3 sampleDirection = 0.f; + float sampleDensity = 0.f; + + SampleLightsOptions options; + options.isDirect = true; + options.receiveShadows = receiveShadows; + options.shadowRayMask = ShadowRayMask(); + options.lightsRenderingLayerMask = 0xFFFFFFFF; + options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS); + + LightSample lightSample = (LightSample)0; + if (SampleLightsRadiance(dispatchInfo, accelStruct, instanceList, origin, normal, options, rngState, lightSample)) + { + float sampleMISWeight = EmissiveMISWeightForLightRay(lightSample.lightType, lightSample.direction, lightSample.risSourcePdf, normal); + float3 e = sampleMISWeight * ClampedCosine(normal, lightSample.direction) * lightSample.radiance; + + irradiance.rgb += e; + directional += float4(lightSample.direction, 1.f) * Luminance(e); + } +} + +// Sample a random cosine weighted direction, weight with MIS. +// Assumes 2 sampling strategies, the other being direct sampling of points on each light. +// Used to avoid extra noise when light sampling is undesirable. +void EstimateMISWeightedIrradianceUsingCosineSampling( + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + StructuredBuffer instanceList, + float3 origin, + float3 normal, + float2 rng, + inout float4 irradiance, + inout float4 directional) +{ + UnifiedRT::Ray ray; + ray.origin = origin; + ray.direction = CosineSample(rng, normal); + ray.tMin = 0; + ray.tMax = FLT_MAX; + + float sampleDensity = dot(normal, ray.direction) / PI; + float lightDensity = 0.f; + float3 emission = 0.f; + float3 attenuation = 1.0f; + bool hitSurface = false; + + for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++) + { + UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0); + if (hitResult.IsValid()) // Hit something, possibly emissive + { + UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID); + PTHitGeom geometry = GetHitGeomInfo(instance, hitResult); + geometry.FixNormals(ray.direction); + MaterialProperties material = LoadMaterialProperties(instance, false, geometry); + + // Transmissive material, continue ray and attenuate + if (material.isTransmissive) + { + attenuation *= saturate(material.transmission); + ray.origin = geometry.NextTransmissionRayOrigin(); + continue; + } + hitSurface = true; + + // Hit emissive frontface + if (!ShouldTreatAsBackface(hitResult, material) && any(material.emissive)) + { + lightDensity = ComputeMeshLightDensity(instanceList, geometry, hitResult.instanceID, ray.origin); + emission = material.emissive; + break; + } + } + } + + if (!hitSurface) // Hit environment + { + GetEnvironmentLightEmissionAndDensity(ray.direction, emission, lightDensity); + } + + // Tint the emissiom by the transmissive attenuation before accumulating + emission *= attenuation; + + // MIS - Cosine weighted sampling + float sampleMISWeight = EmissiveMISWeightForBrdfRay(lightDensity, sampleDensity); + + // f(x)/p(x) = (Li * cos(theta)) / (cos(theta) / PI) = Li * PI + float3 e = emission * PI * sampleMISWeight; + + irradiance.rgb += e; + directional += float4(ray.direction, 1.f) * Luminance(e); +} + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + const uint sampleOffset = g_SampleOffset + localSampleOffset; + + float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff); + + float4 irradiance = 0.f; + float4 directional = 0.f; + + PathTracingSampler rngState; + rngState.Init(instanceTexelPos, sampleOffset); + + #if (EMISSIVE_SAMPLING != BRDF_SAMPLING) + EstimateMISWeightedIrradianceUsingDirectSampling(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, g_ReceiveShadows, rngState, irradiance, directional); + #endif + + #if (EMISSIVE_SAMPLING != LIGHT_SAMPLING) + float2 rng = float2(rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_X), rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_Y)); + EstimateMISWeightedIrradianceUsingCosineSampling(dispatchInfo, accelStruct, g_AccelStructInstanceList, origin, worldNormal, rng, irradiance, directional); + #endif + + // store new accumulated irradiance + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(irradiance.rgb, 1.0f); + if (g_AccumulateDirectional > 0) + g_ExpandedDirectional[dispatchInfo.dispatchThreadID.x] += directional; +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader.meta new file mode 100644 index 00000000000..6d2a05cda17 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapDirectIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 5bbebdef5916845478fcef13fa10d89f +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader new file mode 100644 index 00000000000..c8a5958a712 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader @@ -0,0 +1,21 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +RWStructuredBuffer g_LightmapSamplesExpanded; + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + float2 uv1 = 0.f; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, worldPosition, worldNormal, worldFaceNormal, uv1); + if (!gotSample) + return; + g_LightmapSamplesExpanded[dispatchInfo.dispatchThreadID.x] = uv1; +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader.meta new file mode 100644 index 00000000000..ca3634f2741 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferDebug.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 737a1597422c12547a8714d76a9406ec +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader new file mode 100644 index 00000000000..0b99eee8adb --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader @@ -0,0 +1,96 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRayAndQueryHit.hlsl" +#include "PathTracingRandom.hlsl" +#include "StochasticLightmapSampling.hlsl" + +UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_UVAccelStruct); +int g_StochasticAntialiasing; +int g_SuperSampleWidth; // width of the super sampling grid - defines as g_SuperSampleWidth x g_SuperSampleWidth grid of samples +int g_InstanceOffsetX; +int g_InstanceOffsetY; +int g_InstanceWidth; +int g_InstanceHeight; +float g_InstanceWidthScale; +float g_InstanceHeightScale; +int g_ChunkOffsetX; +int g_ChunkOffsetY; +int g_ChunkSize; +int g_ExpandedTexelSampleWidth; +int g_PassSampleCount; +int g_SampleOffset; +Texture2D g_UvFallback; +RWStructuredBuffer g_GBuffer; +StructuredBuffer g_CompactedGBuffer; +StructuredBuffer g_CompactedGBufferLength; + +float2 JitterSample(float2 sample, float2 random01, float jitterAmount) +{ + const float2 jitteredSample = sample + jitterAmount * (random01 - 0.5f); + return saturate(jitteredSample); +} + +float2 GetSuperSamplingOffset(uint currentSuperSampleIndex, uint superSampleWidth) +{ + currentSuperSampleIndex = currentSuperSampleIndex % (superSampleWidth * superSampleWidth); + uint ssX = currentSuperSampleIndex % superSampleWidth; + uint ssY = currentSuperSampleIndex / superSampleWidth; + return (float2(ssX, ssY) + 0.5f) / (float)superSampleWidth; +} + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + // The dispatch domain is [0; (g_ChunkSize * g_ExpandedTexelSampleWidth) - 1] in X + const uint sampleIndex = dispatchInfo.dispatchThreadID.x; + g_GBuffer[sampleIndex].instanceID = -1; + g_GBuffer[sampleIndex].primitiveIndex = -1; + g_GBuffer[sampleIndex].barycentrics = uint2(0,0); + + const uint compactedRange = g_CompactedGBufferLength[0] * (uint)g_ExpandedTexelSampleWidth; + if (sampleIndex >= compactedRange) + return; + + // check that the sample is needed, g_ExpandedTexelSampleWidth might have more samples than needed as it is power of two + // .. and the last sampling pass might have few remaining samples + uint localSampleIndex = (uint)sampleIndex % (uint)g_ExpandedTexelSampleWidth; + if (localSampleIndex >= (uint)g_PassSampleCount) + return; + + uint compactedTexelIndex = sampleIndex / (uint)g_ExpandedTexelSampleWidth; + uint chunkLinearTexelIndex = g_CompactedGBuffer[compactedTexelIndex]; + if (chunkLinearTexelIndex >= (uint)g_ChunkSize) + return; + + const uint linearChunkOffset = g_ChunkOffsetX + g_ChunkOffsetY * g_InstanceWidth; + const uint linearInstancePos = linearChunkOffset + chunkLinearTexelIndex; + if (linearInstancePos >= (uint)g_InstanceWidth * (uint)g_InstanceHeight) + return; + + const uint2 instanceTexelPos = uint2(linearInstancePos % g_InstanceWidth, linearInstancePos / g_InstanceWidth); + const uint2 lightmapTexelPos = uint2(g_InstanceOffsetX, g_InstanceOffsetY) + instanceTexelPos; + const float2 instanceSize = float2(g_InstanceWidth, g_InstanceHeight); + + UnifiedRT::RayTracingAccelStruct uvAccelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_UVAccelStruct); + + PathTracingSampler rngState; + rngState.Init(lightmapTexelPos, g_SampleOffset + localSampleIndex); + + // select a random point in the texel + const float2 random01 = float2(rngState.GetFloatSample(RAND_DIM_AA_X), rngState.GetFloatSample(RAND_DIM_AA_Y)); + const float2 texelSample = g_StochasticAntialiasing == 1 ? random01 : JitterSample(GetSuperSamplingOffset(g_SampleOffset + localSampleIndex, g_SuperSampleWidth), random01, 0.001f); // the jitter is to avoid issues with raytracing watertightness + const float2 instanceScale = float2(g_InstanceWidthScale, g_InstanceHeightScale); + const UnifiedRT::Hit hit = LightmapSampleTexelOffset(instanceTexelPos, texelSample, instanceSize, instanceScale, dispatchInfo, uvAccelStruct, g_UvFallback[instanceTexelPos]); + + if (!hit.IsValid()) + { + // no intersection found + return; + } + g_GBuffer[sampleIndex].instanceID = hit.instanceID; + g_GBuffer[sampleIndex].primitiveIndex = hit.primitiveIndex; + g_GBuffer[sampleIndex].barycentrics = hit.uvBarycentrics; +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader.meta new file mode 100644 index 00000000000..5e296643f6e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: e9f4de6d1e07fb94f9a60d35b618f724 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader new file mode 100644 index 00000000000..e7b69c1165b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader @@ -0,0 +1,132 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_AccumulateDirectional; +int g_SampleOffset; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; +RWStructuredBuffer g_ExpandedDirectional; + +// additional instance flags to deal with lightmap lods +#define CURRENT_LOD_FOR_LIGHTMAP_INSTANCE 8u +#define LOD_ZERO_FOR_LIGHTMAP_INSTANCE 16u +#define CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW 32u +#define LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW 64u + +float3 EstimateLightmapRadiance(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::Ray ray, inout PathTracingSampler rngState) +{ + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + PathIterator pathIter; + InitPathIterator(pathIter, ray); + + // LOD handling: Rays starting from the lightmapped object use an accel struct built with that same LOD level + uint rayLightmapLodMask = CURRENT_LOD_FOR_LIGHTMAP_INSTANCE; + uint shadowRayLightmapLodMask = CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW; + + int transparencyBounce = 0; + // We start at bounce index 1, as bounce index is defined relative to the camera for this path tracer. + // Since this function is used for baking, we already implicitly have the first "hit", and are about + // to process the second path segment. + for (int bounceIndex = 1; bounceIndex <= g_BounceCount && transparencyBounce < MAX_TRANSMISSION_BOUNCES; bounceIndex++) + { + // The first path segment is special for the indirect pass - we should not add radiance from the + // environment or emission, as these are already explicitly sampled in the direct pass. + bool isFirstPathSegment = bounceIndex == 1; + + uint pathRayMask = RayMask(bounceIndex == 0) | rayLightmapLodMask; + uint traceResult = TraceBounceRay(pathIter, bounceIndex, pathRayMask, dispatchInfo, accelStruct, rngState); + + if (traceResult == TRACE_HIT) + { + uint hitInstanceMask = UnifiedRT::GetInstance(pathIter.hitResult.instanceID).instanceMask; + + // LOD handling: If the ray hits a surface that is not the current lightmap instance, + // then, for the rest of the path, we can replace it in the scene accel struct by one that using lod 0. + if (!(hitInstanceMask & CURRENT_LOD_FOR_LIGHTMAP_INSTANCE)) + { + rayLightmapLodMask = LOD_ZERO_FOR_LIGHTMAP_INSTANCE; + shadowRayLightmapLodMask = LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW; + } + + uint shadowRayMask = ShadowRayMask() | shadowRayLightmapLodMask; + if (!isFirstPathSegment) + AddEmissionRadiance(pathIter, accelStruct, g_AccelStructInstanceList, false); + AddRadianceFromDirectIllumination(pathIter, shadowRayMask, dispatchInfo, accelStruct, g_AccelStructInstanceList, rngState, false); + } + + if (traceResult == TRACE_MISS) + { + if (!isFirstPathSegment) + AddEnvironmentRadiance(pathIter, false); + break; + } + + if (traceResult == TRACE_TRANSMISSION) + { + bounceIndex--; + transparencyBounce++; + pathIter.ray.origin = pathIter.hitGeo.NextTransmissionRayOrigin(); + pathIter.throughput *= pathIter.material.transmission; + rngState.NextBounce(); + continue; + } + + if (!Scatter(pathIter, rngState)) + break; + + if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES) + { + float p = max(pathIter.throughput.x, max(pathIter.throughput.y, pathIter.throughput.z)); + if (rngState.GetFloatSample(RAND_DIM_RUSSIAN_ROULETTE) > p) + break; + else + pathIter.throughput /= p; + } + + rngState.NextBounce(); + } + + return pathIter.radianceSample; +} + + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + // now sample irradiance with the gBuffer data + const uint sampleOffset = g_SampleOffset + localSampleOffset; + PathTracingSampler rngState; + rngState.Init(instanceTexelPos, sampleOffset); + UnifiedRT::Ray ray; + ray.origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff); + ray.direction = CosineSample(float2(rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_X), rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_Y)), worldNormal); + ray.tMin = 0; + ray.tMax = Max_float(); + rngState.NextBounce(); + + float3 sampleRadiance = EstimateLightmapRadiance(dispatchInfo, ray, rngState); + + const float sampleLuminance = Luminance(sampleRadiance.xyz); + const float reciprocalProbabilityDensityMulipliedByCosine = PI; + + // store new accumulated radiance + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(sampleRadiance * reciprocalProbabilityDensityMulipliedByCosine, 1.0f); + // the cosine term from the PDF cancels with the cosine term for the integrand. + if (g_AccumulateDirectional > 0) + g_ExpandedDirectional[dispatchInfo.dispatchThreadID.x] += float4(normalize(ray.direction), 1.f) * sampleLuminance; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader.meta new file mode 100644 index 00000000000..1413d9e0f43 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIndirectIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 7624adbeb148b5c48ab5ab660cc2602a +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl new file mode 100644 index 00000000000..80abd92bcee --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl @@ -0,0 +1,65 @@ +#ifndef _PATHTRACING_LIGHTMAPINTEGRATIONHELPERS_HLSL_ +#define _PATHTRACING_LIGHTMAPINTEGRATIONHELPERS_HLSL_ + +#include "PathTracingCommon.hlsl" + +StructuredBuffer g_GBuffer; +RWStructuredBuffer g_CompactedGBuffer; +RWStructuredBuffer g_CompactedGBufferLength; // This will contain the number of texels written. +int g_MaxLocalSampleCount; // Don't take more than this many samples per texel in this dispatch, sometimes the last dispatch will have fewer samples as the expanded sample count may not be a multiple of the total sample count +int g_ExpandedTexelSampleWidth; // The number of samples per texel in the expanded buffers +int g_InstanceWidth; +int g_ChunkOffsetX; +int g_ChunkOffsetY; +int g_InstanceGeometryIndex; +float4x4 g_ShaderLocalToWorld; +float4x4 g_ShaderLocalToWorldNormals; + +bool GetExpandedSample(uint dispatchIndex, out uint localSampleOffset, out uint2 instanceTexelPos, inout float3 worldPosition, inout float3 worldNormal, inout float3 worldFaceNormal) +{ + localSampleOffset = dispatchIndex % g_ExpandedTexelSampleWidth; + instanceTexelPos = 0; + if (localSampleOffset >= (uint)g_MaxLocalSampleCount) + return false; // no more samples to process + + const uint compactedTexelIndex = dispatchIndex / g_ExpandedTexelSampleWidth; + const uint texelIndex = g_CompactedGBuffer[compactedTexelIndex]; + const uint linearChunkOffset = g_ChunkOffsetY * g_InstanceWidth + g_ChunkOffsetX; + const uint linearTexelIndex = texelIndex + linearChunkOffset; + instanceTexelPos = uint2(linearTexelIndex % g_InstanceWidth, linearTexelIndex / g_InstanceWidth); + + if (!g_GBuffer[dispatchIndex].IsValid()) + return false; // no intersection found, skip this sample + + UnifiedRT::Hit hit; + hit.instanceID = g_GBuffer[dispatchIndex].instanceID; + hit.primitiveIndex = g_GBuffer[dispatchIndex].primitiveIndex; + hit.uvBarycentrics = g_GBuffer[dispatchIndex].barycentrics; + + FetchGeomAttributes(hit, g_InstanceGeometryIndex, worldPosition, worldNormal, worldFaceNormal); + + worldPosition = mul(g_ShaderLocalToWorld, float4(worldPosition, 1)).xyz; + worldNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldNormal)); + worldFaceNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldFaceNormal)); + return true; +} + +bool GetExpandedSample(uint dispatchIndex, inout float3 worldPosition, inout float3 worldNormal, inout float3 worldFaceNormal, inout float2 uv1) +{ + if (!g_GBuffer[dispatchIndex].IsValid()) + return false; // no intersection found, skip this sample + + UnifiedRT::Hit hit; + hit.instanceID = g_GBuffer[dispatchIndex].instanceID; + hit.primitiveIndex = g_GBuffer[dispatchIndex].primitiveIndex; + hit.uvBarycentrics = g_GBuffer[dispatchIndex].barycentrics; + + FetchGeomAttributes(hit, g_InstanceGeometryIndex, worldPosition, worldNormal, worldFaceNormal, uv1); + + worldPosition = mul(g_ShaderLocalToWorld, float4(worldPosition, 1)).xyz; + worldNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldNormal)); + worldFaceNormal = normalize(mul((float3x3)g_ShaderLocalToWorldNormals, worldFaceNormal)); + return true; +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl.meta new file mode 100644 index 00000000000..b9a03b8e0ad --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapIntegrationHelpers.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4d15bddf10493684cbf573c1b43be6c8 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader new file mode 100644 index 00000000000..8afdad73b27 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader @@ -0,0 +1,24 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_SampleOffset; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; +float g_AOMaxDistance; + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] = float4(worldNormal, 1.0f); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader.meta new file mode 100644 index 00000000000..f635ff97bf4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapNormalIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 1451d029813b192429211be28181beab +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute new file mode 100644 index 00000000000..8e89fa331b1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute @@ -0,0 +1,24 @@ +RWTexture2D g_LightmapInOut; +Texture2D g_UvFallback; +int g_InstanceOffsetX; +int g_InstanceOffsetY; +float g_InstanceWidthScale; +float g_InstanceHeightScale; +uint g_InstanceWidth; +uint g_InstanceHeight; + +#pragma kernel BlitOccupancy +[numthreads(8,8,1)] +void BlitOccupancy(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= (uint) g_InstanceWidth || id.y >= (uint)g_InstanceHeight) + return; + + uint2 instanceTexelPos = id.xy; + uint2 lightmapTexelPos = uint2(g_InstanceOffsetX, g_InstanceOffsetY) + instanceTexelPos; + + if (g_UvFallback[instanceTexelPos].x < 0) + g_LightmapInOut[lightmapTexelPos] = 0.f; + else + g_LightmapInOut[lightmapTexelPos] = 1.f; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute.meta new file mode 100644 index 00000000000..7b93c36160e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapOccupancy.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 891acea040e321148ab512076f084f6c +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader new file mode 100644 index 00000000000..fcb880ca975 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader @@ -0,0 +1,62 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_SampleOffset; +uint g_ReceiveShadows; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; +RWStructuredBuffer g_ExpandedSampleCountInW; + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + // now sample direct lighting with the gBuffer data + const uint sampleOffset = g_SampleOffset + localSampleOffset; + PathTracingSampler rngState; + rngState.Init(instanceTexelPos, sampleOffset); + + // now sample direct lighting with the gBuffer data + uint numLights = g_NumLights; + uint dimsOffset = 2; // first two dimensions are used for the stochastic gBuffer sampling + float visibility[4] = { 0.f, 0.f, 0.f, 0.f }; + for (uint lightIndex = 0; lightIndex < numLights; ++lightIndex) + { + PTLight light = FetchLight(lightIndex); + if (light.shadowMaskChannel == -1) + continue; + if (light.type == EMISSIVE_MESH || light.type == ENVIRONMENT_LIGHT) // Shadowmask only makes sense for punctual lights + continue; + uint dimsUsed = 0; + + float3 origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff); + + float3 attenuation = 1.0f; + float isVisible = IsLightVisibleFromPoint(dispatchInfo, accelStruct, g_AccelStructInstanceList, ShadowRayMask(), origin, rngState, dimsOffset, dimsUsed, light, g_ReceiveShadows, attenuation) ? dot(float3(1.0f, 1.0f, 1.0f), attenuation)/3.0f : 0.0f; + // always start at an even dimension + if (dimsUsed % 2 == 1) + dimsUsed++; + dimsOffset += dimsUsed; + if (light.shadowMaskChannel < 0 || light.shadowMaskChannel >= 4) + continue; + visibility[light.shadowMaskChannel] += isVisible; + } + + // store new accumulated radiance + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(visibility[0], visibility[1], visibility[2], visibility[3]); + g_ExpandedSampleCountInW[dispatchInfo.dispatchThreadID.x] += float4(0.f, 0.f, 0.f, 1.f); +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader.meta new file mode 100644 index 00000000000..d71ce9030d4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapShadowMaskIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: d19f4f8a13b6edb48b986a47ec96402e +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader new file mode 100644 index 00000000000..3d273d5eb22 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader @@ -0,0 +1,71 @@ +#define UNIFIED_RT_GROUP_SIZE_X 64 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC AccumulateInternal + +#include "PathTracing.hlsl" +#include "LightmapIntegrationHelpers.hlsl" + +int g_SampleOffset; +float g_PushOff; +RWStructuredBuffer g_ExpandedOutput; + +void AccumulateInternal(UnifiedRT::DispatchInfo dispatchInfo) +{ + float3 worldPosition = 0.f; + float3 worldNormal = 0.f; + float3 worldFaceNormal = 0.f; + uint localSampleOffset = 0; + uint2 instanceTexelPos = 0; + const bool gotSample = GetExpandedSample(dispatchInfo.dispatchThreadID.x, localSampleOffset, instanceTexelPos, worldPosition, worldNormal, worldFaceNormal); + if (!gotSample) + return; + + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + // setup rng + const uint sampleOffset = g_SampleOffset + localSampleOffset; + PathTracingSampler rngState; + rngState.Init(instanceTexelPos, sampleOffset); + + // now sample validity with the gBuffer data + float invalidity = 0.f; + UnifiedRT::Ray ray; + ray.origin = OffsetRayOrigin(worldPosition, worldFaceNormal, g_PushOff); + const float3 direction = CosineSample(float2(rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_X), rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_Y)), worldNormal); + ray.direction = direction; + ray.tMin = 0; + ray.tMax = Max_float(); + rngState.NextBounce(); + + // trace through potentially several layers of transmissive materials, determining validity at each hit + for (uint i = 0; i < MAX_TRANSMISSION_BOUNCES; i++) + { + UnifiedRT::Hit hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, RayMask(true), ray, 0); + if (!hitResult.IsValid()) + break; // Hit nothing, so the ray is valid + + // Something was hit + UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID); + PTHitGeom geometry = GetHitGeomInfo(instance, hitResult); + MaterialProperties material = LoadMaterialProperties(instance, false, geometry); + + // Check for transmission + if (ShouldTransmitRay(rngState, material)) + { + geometry.FixNormals(direction); + ray.origin = geometry.NextTransmissionRayOrigin(); + rngState.NextBounce(); + continue; + } + + // Check for validity + bool treatAsBackFace = ShouldTreatAsBackface(hitResult, material); + if (!treatAsBackFace) + break; // we have a valid hit + + // Hit was invalid and there is no transmission + invalidity += 1.f; + break; + } + g_ExpandedOutput[dispatchInfo.dispatchThreadID.x] += float4(invalidity, invalidity, invalidity, 1.0f); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader.meta new file mode 100644 index 00000000000..e52b1218aab --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapValidityIntegration.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: ace95ac84e213794891b4bd7c9b3c158 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping.meta new file mode 100644 index 00000000000..d91a03b8a07 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ea04c59408e02c948b94f13808d4591c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute new file mode 100644 index 00000000000..0cd39147e24 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute @@ -0,0 +1,91 @@ +#pragma exclude_renderers webgpu // No atomics + +#pragma kernel MarkBilinearOverlaps + +#define UINT_MAX 4294967295u +#define NO_CHART UINT_MAX + +uint _TileSize; +uint _TileX; +uint _TileY; + +uint _InstanceIndex; +uint _EdgeCount; +StructuredBuffer _TriangleEdges; // Per thread. Edge of each triangle (startX, startY, endX, endY) +StructuredBuffer _ChartIndices; // Per thread chart index. + +uint _TextureSize; +RWStructuredBuffer _PerPixelChart; // Per pixel chart index. +RWStructuredBuffer _OverlapPixels; // Per pixel, no overlap = 0, overlap = 1 +RWStructuredBuffer _OverlapInstances; // Per instance, no overlap = 0, overlap = 1 + +[numthreads(128,1,1)] +void MarkBilinearOverlaps(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= _EdgeCount) + return; + + // Tile the dispatch so we don't do too much work on each thread + uint2 tilePos = uint2(_TileX, _TileY) * _TileSize; + uint2 tileEnd = tilePos + _TileSize; + + // Fetch chart and triangle edge for this thread. + uint chartIdx = _ChartIndices[id.x]; + float4 edge = _TriangleEdges[id.x]; + float2 startPos = edge.xy; + float2 endPos = edge.zw; + + uint2 aabbStartPos = max(uint2(floor(min(startPos, endPos) - 0.5f)), 0); + uint2 aabbEndPos = min(uint2(ceil(max(startPos, endPos) + 0.5f)), _TextureSize-1); + aabbStartPos = clamp(aabbStartPos, tilePos, tileEnd); + aabbEndPos = clamp(aabbEndPos, tilePos, tileEnd); + uint2 extent = aabbEndPos - aabbStartPos; + + // The edge is described by a line A*x + B*y + C = 0. + float lineA = endPos.y - startPos.y; + float lineB = startPos.x - endPos.x; + float lineC = startPos.y * endPos.x - startPos.x * endPos.y; + + // For every pixel in the AABB... + [allow_uav_condition] + for (uint y = 0; y < extent.y; y++) + { + [allow_uav_condition] + for (uint x = 0; x < extent.x; x++) + { + uint2 pixelCoord = uint2(x, y) + aabbStartPos; + float2 bottomLeftCornerPos = pixelCoord - 0.5f; + + // For each corner of the bilinear neighborhood... + bool4 cornerMask = false; + [unroll] + for (uint corner = 0; corner < 4; corner++) + { + uint offsetX = 2 * (corner & 1); + uint offsetY = corner & 2; + float2 cornerPos = bottomLeftCornerPos + float2(offsetX, offsetY); + // Check if the corner is below or above the edge. + cornerMask[corner] = lineA * cornerPos.x + lineB * cornerPos.y + lineC > 0; + } + + // If some, but not all, corners of the bilinear neighborhood are above the edge, + // the bilinear neighborhood is occupied - mark it. + [branch] + if (any(cornerMask) && !all(cornerMask)) + { + uint pixelIndex = pixelCoord.y * _TextureSize + pixelCoord.x; + + // Try to write assuming there was no chart already present. + uint prevChart; + InterlockedCompareExchange(_PerPixelChart[pixelIndex], NO_CHART, chartIdx, prevChart); + + // If there was already a chart present, and it wasn't this threads chart, we have an overlap, so mark it. + if (prevChart != NO_CHART && prevChart != chartIdx) + { + _OverlapPixels[pixelIndex] = 1; + _OverlapInstances[_InstanceIndex] = 1; + } + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute.meta new file mode 100644 index 00000000000..8fb2443f167 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/BilinearOverlaps.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1a030e4984617e340bf47a116a1d0f9c +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader new file mode 100644 index 00000000000..7ea04375613 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader @@ -0,0 +1,43 @@ +Shader "Hidden/ChartRasterizerHardware" +{ + SubShader + { + Pass + { + Cull Off + Conservative On + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + + struct v2f + { + float4 vertex : SV_POSITION; + nointerpolation uint chartId : TEXCOORD0; + }; + + float4 g_ScaleAndOffset; + uint g_ChartIndexOffset; + StructuredBuffer g_VertexToChartID; + + v2f vert (float4 vertex : POSITION, uint vertexId : SV_VertexID) + { + v2f o; + o.vertex = float4((vertex.xy * g_ScaleAndOffset.xy + g_ScaleAndOffset.zw) * 2-1, 0, 1); + #if UNITY_UV_STARTS_AT_TOP + o.vertex.y = -o.vertex.y; + #endif + o.chartId = g_VertexToChartID[vertexId]; + return o; + } + + float frag (v2f i) : SV_Target + { + return g_ChartIndexOffset + i.chartId; + } + ENDCG + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader.meta new file mode 100644 index 00000000000..1dea5714b0c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerHardware.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ccc7ffe6cf9c542919823f6486edde9d +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader new file mode 100644 index 00000000000..d4a3af9887e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader @@ -0,0 +1,59 @@ +Shader "Hidden/ChartRasterizerSoftware" +{ + SubShader + { + Pass + { + Cull Off + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "GeometryUtils.hlsl" + + struct v2f + { + float4 vertex : SV_POSITION; + nointerpolation uint chartId : TEXCOORD0; + nointerpolation float4 aabb : TEXCOORD1; + }; + + uint g_Width; + uint g_Height; + float4 g_ScaleAndOffset; + uint g_ChartIndexOffset; + StructuredBuffer g_VertexBuffer; + StructuredBuffer g_VertexToOriginalVertex; + StructuredBuffer g_VertexToChartID; + + v2f vert (uint vertexId : SV_VertexID) + { + v2f o; + + // Expand the triangle. + uint2 resolution = uint2(g_Width, g_Height); + float2 tri[3]; + ReadParentTriangle(g_VertexBuffer, vertexId, g_ScaleAndOffset, tri); + ExpandTriangleForConservativeRasterization(resolution, tri, vertexId, o.aabb, o.vertex); + + // Get the chart index. + uint originalVertexId = g_VertexToOriginalVertex[vertexId]; + o.chartId = g_VertexToChartID[originalVertexId]; + + return o; + } + + float frag (v2f i) : SV_Target + { + // Clip overly conservative edges. + float2 pos = i.vertex.xy; + if (pos.x < i.aabb.x || pos.y < i.aabb.y || + pos.x > i.aabb.z || pos.y > i.aabb.w) + discard; + + return g_ChartIndexOffset + i.chartId; + } + ENDCG + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader.meta new file mode 100644 index 00000000000..51a2a626ae4 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/ChartRasterizerSoftware.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c1e7adc3cf9b84be0b845973cedd828b +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl new file mode 100644 index 00000000000..a152f042ee2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl @@ -0,0 +1,156 @@ +// Given a triangle edge defined by 2 verts, calculate 2 new verts that define the same edge, +// pushed outwards enough to cover centroids of any pixels which may intersect the triangle. +void OffsetEdge(float2 v1, float2 v2, float2 pixelSize, out float2 v1Offset, out float2 v2Offset) +{ + // Find the normal of the edge + float2 edge = v2 - v1; + float2 normal = normalize(float2(-edge.y, edge.x)); + + // Find the amount to offset by. This is the semidiagonal of the pixel box in the same quadrant as the normal. + float2 semidiagonal = pixelSize / sqrt(2.0); + semidiagonal *= float2(normal.x > 0 ? 1 : -1, normal.y > 0 ? 1 : -1); + + // Offset the edge + v1Offset = v1 + semidiagonal; + v2Offset = v2 + semidiagonal; +} + +// Given 2 lines defined by 2 points each, find the intersection point. +float2 LineIntersect(float2 p1, float2 p2, float2 p3, float2 p4) +{ + // Line p1p2 represented as a1x + b1y = c1 + float a1 = p2.y - p1.y; + float b1 = p1.x - p2.x; + float c1 = a1 * p1.x + b1 * p1.y; + + // Line p3p4 represented as a2x + b2y = c2 + float a2 = p4.y - p3.y; + float b2 = p3.x - p4.x; + float c2 = a2 * p3.x + b2 * p3.y; + + float determinant = a1 * b2 - a2 * b1; + if (determinant == 0) // Parallel lines - return any valid point. + return p1; + + float x = b2 * c1 - b1 * c2; + float y = a1 * c2 - a2 * c1; + return float2(x, y) / determinant; +} + +// Check which side the point p is on, relative to the line cp1-cp2. +// Optionally with an epsilon value within which points are considered outside. +bool IsInside(float2 p, float2 cp1, float2 cp2, float eps = 0.0f) +{ + return (cp2.x - cp1.x) * (p.y - cp1.y) - (cp2.y - cp1.y) * (p.x - cp1.x) >= eps; +} + +// Clip a polygon with a line defined by two points, in place. +// https://en.wikipedia.org/wiki/Sutherland-Hodgman_algorithm +void ClipPolygonWithLine(inout float2 polygon[6], inout uint polygonLength, float2 cp1, float2 cp2) +{ + float2 result[6]; + uint resultLength = 0; + + for (uint i = 0; i < polygonLength; i++) + { + float2 s = polygon[i]; + float2 e = polygon[(i + 1) % polygonLength]; + + if (IsInside(e, cp1, cp2)) // At least one endpoint is inside + { + if (!IsInside(s, cp1, cp2)) // Only the end point is inside, add the intersection + { + result[resultLength] = LineIntersect(cp1, cp2, s, e); + resultLength++; + } + result[resultLength] = e; + resultLength++; + } + else if (IsInside(s, cp1, cp2)) // Only the start point is inside, add the intersection + { + result[resultLength] = LineIntersect(cp1, cp2, s, e); + resultLength++; + } + } + + polygonLength = resultLength; + for (uint p = 0; p < polygonLength; p++) + polygon[p] = result[p]; +} + +// Clip a polygon to the bounding box of a texel at the given position +bool ClipPolygonWithTexel(float2 texelPosition, inout float2 polygon[6], inout uint polygonLength) +{ + // Get the bounding box of the texel to clip with. + float2 clipPolygon[4] = + { + texelPosition + float2(-0.5, -0.5), + texelPosition + float2(0.5, -0.5), + texelPosition + float2(0.5, 0.5), + texelPosition + float2(-0.5, 0.5), + }; + + // Clip to each edge of the quad + for (uint edge = 0; edge < 4; edge++) + { + ClipPolygonWithLine(polygon, polygonLength, clipPolygon[edge], clipPolygon[(edge + 1) % 4]); + } + + return polygonLength > 0; +} + +// Read the parent triangle of a vertex from a vertex buffer, and transform it to the desired space. +void ReadParentTriangle(StructuredBuffer vertexBuffer, uint vertexId, float4 scaleAndOffset, out float2 tri[3]) +{ + uint triId = vertexId / 3; + uint baseVertexId = triId * 3; + tri[0] = vertexBuffer[baseVertexId + 0].xy * scaleAndOffset.xy + scaleAndOffset.zw; + tri[1] = vertexBuffer[baseVertexId + 1].xy * scaleAndOffset.xy + scaleAndOffset.zw; + tri[2] = vertexBuffer[baseVertexId + 2].xy * scaleAndOffset.xy + scaleAndOffset.zw; + + // Flip the winding order if it doesn't follow Unity's convention + float2 edgeAB = tri[1] - tri[0]; + float2 edgeAC = tri[2] - tri[0]; + bool flipWinding = ((edgeAB.x * edgeAC.y) - (edgeAB.y * edgeAC.x)) > 0; + + if (flipWinding) + { + float2 temp = tri[1]; + tri[1] = tri[2]; + tri[2] = temp; + } +} + +// Given an effective resolution, a triangle, and a specific vertex [0; 2], calculate a new position for the +// vertex, which will result in conservative rasterization when fed to a non-conservative rasterizer. +void ExpandTriangleForConservativeRasterization( + uint2 resolution, + float2 tri[3], + uint vertexId, + out float4 triangleAABB, + out float4 vertex) +{ + float2 pixelSize = (1.0 / resolution); + + // Calculate the AABB to clip off the overly conservative edges + float2 aabbMin = min(min(tri[0], tri[1]), tri[2]); + float2 aabbMax = max(max(tri[0], tri[1]), tri[2]); + triangleAABB = (float4(aabbMin - (pixelSize / 2), aabbMax + (pixelSize / 2))) * resolution.xyxy; + + // Get offset lines + float2 v1Off1, v2Off1; + OffsetEdge(tri[0], tri[1], pixelSize, v1Off1, v2Off1); + float2 v2Off2, v3Off2; + OffsetEdge(tri[1], tri[2], pixelSize, v2Off2, v3Off2); + float2 v3Off3, v1Off3; + OffsetEdge(tri[2], tri[0], pixelSize, v3Off3, v1Off3); + + // Find their intersections. This is the new triangle + tri[0] = LineIntersect(v1Off1, v2Off1, v3Off3, v1Off3); + tri[1] = LineIntersect(v2Off2, v3Off2, v1Off1, v2Off1); + tri[2] = LineIntersect(v3Off3, v1Off3, v2Off2, v3Off2); + vertex = float4(tri[vertexId % 3]*2-1, 0, 1); + #if UNITY_UV_STARTS_AT_TOP + vertex.y = -vertex.y; + #endif +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl.meta new file mode 100644 index 00000000000..50223c43f62 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/GeometryUtils.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: babbf12d148f78a4ebc8b231ebe54046 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader new file mode 100644 index 00000000000..0d66d5ce48c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader @@ -0,0 +1,102 @@ +Shader "Hidden/UVFallbackBufferGeneration" +{ + SubShader + { + Pass + { + Cull Off + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 5.0 + #include "GeometryUtils.hlsl" + + struct v2f + { + float4 vertex : SV_POSITION; + nointerpolation float2 vertices[3] : TEXCOORD0; + }; + + uint g_Width; + uint g_Height; + float g_WidthScale; + float g_HeightScale; + StructuredBuffer g_VertexBuffer; + + v2f vert (uint vertexId : SV_VertexID) + { + v2f o; + + // Expand the triangle. + uint2 resolution = uint2(g_Width, g_Height); + float2 tri[3]; + float4 unusedAABB; + ReadParentTriangle(g_VertexBuffer, vertexId, float4(g_WidthScale, g_HeightScale, 0, 0), tri); + ExpandTriangleForConservativeRasterization(resolution, tri, vertexId, unusedAABB, o.vertex); + + // Scale the triangle to screen coordinates, pass it to fragment shader. + o.vertices[0] = tri[0] * resolution; + o.vertices[1] = tri[1] * resolution; + o.vertices[2] = tri[2] * resolution; + + return o; + } + + float2 frag (v2f i, out float depth : SV_Depth) : SV_Target + { + float2 a = i.vertices[0]; + float2 b = i.vertices[1]; + float2 c = i.vertices[2]; + + // First check the easy case: If the texel center is inside the triangle, no need to clip at all. Just pick it. + // We need to shrink the triangle by a small epsilon to avoid picking a point precisely on the edge of the triangle, + // as our intersector cannot handle such points. Epsilon chosen to be 1 order of magnitude larger than the maximum + // distance between subsequent floats in range [0; 8192] + float2 texelCenter = i.vertex.xy; + const float eps = 0.01f; + if (IsInside(texelCenter, c, b, eps) && IsInside(texelCenter, b, a, eps) && IsInside(texelCenter, a, c, eps)) + { + #if UNITY_REVERSED_Z + depth = 1.0; + #else + depth = 0.0; + #endif + return 0.5; + } + + // 6 length since clipping a triangle with a quad results in at most a hexagon. + float2 result[6] = { a, b, c, float2(0, 0), float2(0, 0), float2(0, 0) }; + uint resultSize = 3; + + // Clip to the texel. + ClipPolygonWithTexel(texelCenter, result, resultSize); + + // Discard removed triangles. + if (resultSize <= 0) + discard; + + // Use the centroid of the clipped triangle as the UV. + float2 center = float2(0, 0); + for (uint i = 0; i < resultSize; i++) + center += result[i]; + center /= resultSize; + float2 offset = center - floor(texelCenter); + + // If the centroid lies on the boundary of the texel, discard - this is a singularity. + if (offset.x <= 0 || offset.x >= 1.0 || offset.y <= 0 || offset.y >= 1.0) + discard; + + // Use the zbuffer to pick the triangle with the smallest distance from centroid to texel center. + float dist = distance(0.5, offset); + depth = (dist / sqrt(0.5)); + #if UNITY_REVERSED_Z + depth = 1.0 - depth; + #endif + + return offset; + } + ENDCG + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader.meta new file mode 100644 index 00000000000..3b10ceb910f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/Lightmapping/UVFallbackBufferGeneration.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d28d1bc84c83fd54a85bd554d8b5c9c7 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader new file mode 100644 index 00000000000..7ab820b762d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader @@ -0,0 +1,173 @@ +#define UNIFIED_RT_GROUP_SIZE_X 16 +#define UNIFIED_RT_GROUP_SIZE_Y 8 + +RWTexture2D g_Radiance; // Path tracer output +RWTexture2D g_MotionVectors; +RWTexture2D g_NormalsDepth; +RWTexture2D g_DebugOutput; + +// Input +float4x4 g_CameraToWorldMatrix; +float4 g_CameraFrustum; +float4x4 g_CameraViewProjection; +float4x4 g_PreviousViewProjection; + +uint g_SampleCount; +uint g_FrameIndex; +int g_EnableSubPixelJittering; +uint g_EnableDebug; + +#define READ_EXPOSURE_FROM_TEXTURE +#include "PathTracing.hlsl" + +UnifiedRT::Ray GeneratePinholeRay(float2 frameCoord, float2 pixelJitter, uint2 launchDim) +{ + float2 ndcCoords01 = (frameCoord + pixelJitter) / float2(launchDim); + float3 viewDirection = float3( + lerp(g_CameraFrustum.x, g_CameraFrustum.y, ndcCoords01.x), + lerp(g_CameraFrustum.z, g_CameraFrustum.w, ndcCoords01.y), + -1.0); + + viewDirection = normalize(viewDirection); + + // Rotate the ray from view space to world space. + float3 rayDirection = mul((float3x3) g_CameraToWorldMatrix, viewDirection); + + UnifiedRT::Ray ray; + ray.origin = GetColumn(g_CameraToWorldMatrix, 3).xyz; // last column + ray.direction = rayDirection; + ray.tMin = 0; + ray.tMax = K_T_MAX; + + return ray; +} + +UnifiedRT::Ray GeneratePrimaryRay(float2 frameCoord, float2 pixelJitter, uint2 launchDim) +{ + return GeneratePinholeRay(frameCoord, pixelJitter, launchDim); +} + +void ComputeMotionVectorAndDepth(inout PixelState pixel, float2 pixelJitter, float3 worldSpacePos) +{ + float2 frameCoord = pixel.coord + pixelJitter; + float4 projCoords = mul(g_PreviousViewProjection, float4(worldSpacePos, 1.0)); + projCoords.xyz = projCoords.xyz / projCoords.w; + projCoords.xy = (projCoords.xy * 0.5 + 0.5) * float2(pixel.launchDim); + pixel.motionVector = frameCoord - projCoords.xy; + + float4 ndc = mul(g_CameraViewProjection, float4(worldSpacePos, 1.0)); + pixel.depth = ndc.z / ndc.w; +} + +void ComputeMotionVectorAndDepth(inout PixelState pixel, float2 pixelJitter, PTHitGeom hitGeom, UnifiedRT::Ray ray, bool hit) +{ + float3 lastWorldPosition; + if (!hit) + { + float t = 10000 / dot(ray.direction, GetColumn(g_CameraToWorldMatrix, 2).xyz); + lastWorldPosition = ray.origin + t * ray.direction; + } + else + { + lastWorldPosition = hitGeom.lastWorldPosition; + pixel.normal = hitGeom.worldFaceNormal; + } + + ComputeMotionVectorAndDepth(pixel, pixelJitter, lastWorldPosition); +} + + +void TraceNewPath(UnifiedRT::DispatchInfo dispatchInfo, uint sampleIndex, inout PathTracingSampler rngState, inout PixelState pixel) +{ + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + float2 pixelJitter = g_EnableSubPixelJittering ? float2(rngState.GetFloatSample(RAND_DIM_AA_X), rngState.GetFloatSample(RAND_DIM_AA_Y)) - 0.5 : 0.0; + + PathIterator pathIter; + InitPathIterator(pathIter, GeneratePrimaryRay(pixel.coord, pixelJitter, pixel.launchDim)); + + int transparencyBounce = 0; + for (int bounceIndex = 0; bounceIndex <= g_BounceCount && transparencyBounce < MAX_TRANSMISSION_BOUNCES; bounceIndex++) + { + uint traceResult = TraceBounceRayAndAddRadiance(pathIter, bounceIndex, RayMask(bounceIndex == 0), ShadowRayMask(), dispatchInfo, accelStruct, g_AccelStructInstanceList, rngState); + pixel.bounces++; + + if (bounceIndex == 0 && sampleIndex == 0) + ComputeMotionVectorAndDepth(pixel, pixelJitter, pathIter.hitGeo, pathIter.ray, pathIter.hitResult.IsValid()); + + if (traceResult == TRACE_MISS) + { + break; + } + if (traceResult == TRACE_TRANSMISSION) + { + bounceIndex--; + transparencyBounce++; + pathIter.ray.origin = pathIter.hitGeo.NextTransmissionRayOrigin(); + pathIter.throughput *= pathIter.material.transmission; + rngState.NextBounce(); + continue; + } + + if (!Scatter(pathIter, rngState)) + break; + + rngState.NextBounce(); + + } + + pixel.radiance += pathIter.radianceSample; +} + +PixelState SamplePixel(UnifiedRT::DispatchInfo dispatchInfo) +{ + uint2 launchIndex = dispatchInfo.dispatchThreadID.xy; + uint2 launchDim = dispatchInfo.dispatchDimensionsInThreads.xy; + + PixelState pixel = (PixelState) 0; + // flip the image (due to the pinhole camera) + pixel.launchIndex = uint2(launchIndex.x, launchDim.y - launchIndex.y - 1); + pixel.coord = pixel.launchIndex + float2(0.5, 0.5); + pixel.launchDim = launchDim; + + PathTracingSampler rngState; + rngState.Init(pixel.launchIndex, g_FrameIndex * g_SampleCount); + + for (uint sampleIndex = 0; sampleIndex < g_SampleCount; sampleIndex++) + { + TraceNewPath(dispatchInfo, sampleIndex, rngState, pixel); + rngState.NextPath(); + } + + pixel.radiance = pixel.radiance / g_SampleCount; + return pixel; +} + +float4 GetHeatmapColor(int rays) +{ + if (rays <= 2) + return float4(0, 1, 0, 0); + + if (rays <= 3) + return float4(0, 0, 1, 0); + + return float4(1, 0, 0, 0); +} + +void RayGenExecute(UnifiedRT::DispatchInfo dispatchInfo) +{ + float exposureMultiplier = GetCurrentExposureMultiplier(); + + PixelState pixel = SamplePixel(dispatchInfo); + g_Radiance[pixel.launchIndex] = float4(pixel.radiance, 0) * exposureMultiplier; + g_MotionVectors[pixel.launchIndex] = float4(pixel.motionVector.x, pixel.motionVector.y, 0, 0); + g_NormalsDepth[pixel.launchIndex] = float4(pixel.normal, pixel.depth); + if (g_EnableDebug) + { + float4 heat = GetHeatmapColor(pixel.bounces); + + g_Radiance[pixel.launchIndex] = heat;// * exposureMultiplier; + g_DebugOutput[pixel.launchIndex] = heat; + } + +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader.meta new file mode 100644 index 00000000000..2019a8e29b6 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LiveGI.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: ceb7a31b84722d144bbeff1c8cd88769 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader new file mode 100644 index 00000000000..25ea425a53a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader @@ -0,0 +1,74 @@ +Shader "Hidden/PassthroughMetaPass" +{ + Properties + { + [MainTexture] _BaseMap("Texture", 2D) = "white" {} + _EmissionMap("Emission", 2D) = "white" {} + _TransparencyLM("Transmission", 2D) = "white" {} + } + SubShader + { + Pass + { + Name "META" + Tags { "LightMode" = "Meta" } + Cull Off + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma shader_feature _EMISSION + + #include "UnityCG.cginc" + #include "UnityMetaPass.cginc" + + sampler2D _BaseMap; + float4 _BaseMap_ST; + sampler2D _EmissionMap; + float4 _EmissionMap_ST; + + struct appdata + { + float4 vertex : POSITION; + float2 uv1 : TEXCOORD1; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float4 pos : SV_POSITION; + float2 uv1 : TEXCOORD1; + }; + + v2f vert(appdata v) + { + v2f o; + if (unity_MetaVertexControl.x) + { + o.pos.xy = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw; + o.pos.zw = 0; + } + else + { + o.pos = mul(UNITY_MATRIX_VP, float4(v.vertex.xyz, 1.0)); + } + o.uv1 = v.uv1; + return o; + } + + float4 frag(v2f i) : SV_Target + { + if (unity_MetaFragmentControl.x) + { + return tex2Dlod(_BaseMap, float4(TRANSFORM_TEX(i.uv1, _BaseMap), 0, 0)); + } + if (unity_MetaFragmentControl.y) + { + return tex2Dlod(_EmissionMap, float4(TRANSFORM_TEX(i.uv1, _EmissionMap), 0, 0)); + } + return 0; + } + ENDCG + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader.meta new file mode 100644 index 00000000000..2b12e7be7b3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughMetaPass.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9df4b433d2c6be346af640f184d0b4a7 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader new file mode 100644 index 00000000000..47e45427300 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader @@ -0,0 +1,59 @@ +Shader "Hidden/PassthroughSkybox" +{ + Properties + { + [NoScaleOffset] _Tex ("Cubemap", Cube) = "grey" {} + } + + SubShader + { + Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" } + Cull Off ZWrite Off + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 5.0 + + #include "UnityCG.cginc" + + samplerCUBE _Tex; + half4 _Tex_HDR; + + struct appdata_t + { + float4 vertex : POSITION; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float3 texcoord : TEXCOORD0; + UNITY_VERTEX_OUTPUT_STEREO + }; + + v2f vert (appdata_t v) + { + v2f o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + o.vertex = UnityObjectToClipPos(v.vertex); + o.texcoord = v.vertex.xyz; + return o; + } + + float4 frag (v2f i) : SV_Target + { + half4 tex = texCUBE (_Tex, i.texcoord); + half3 c = DecodeHDR (tex, _Tex_HDR); + return half4(c, 1); + } + ENDCG + } + } + + Fallback Off +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader.meta new file mode 100644 index 00000000000..e4fbd818a35 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PassthroughSkybox.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5e4da360906ed014eb18ce666f57cc46 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl new file mode 100644 index 00000000000..1ca885d0c7c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl @@ -0,0 +1,361 @@ + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl" + +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" +#include "TraceRayPathTracing.hlsl" + +#include "PathTracingCommon.hlsl" + +#include "PathTracingRandom.hlsl" + +int g_BounceCount; +uint g_LightEvaluations; +int g_CountNEERayAsPathSegment; // This flag must be enabled for MIS to work properly. +int g_RenderedInstances; +int g_PathtracerAsGiPreviewMode; +uint g_PathTermination; +#include "LightSampling.hlsl" + +UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_SceneAccelStruct); + + +#define RENDER_ALL 0 +#define RENDER_ONLY_STATIC 1 +#define RENDER_ALL_IN_CAMERA_RAYS_THEN_ONLY_STATIC 2 + +#define RAY_TERMINATION 0 +#define RAY_SCATTERING 1 + +int ScatterDiffusely(PTHitGeom hitGeom, float3 V, inout PathTracingSampler rngState, out UnifiedRT::Ray bounceRay, out float brdfPdf) +{ + float2 u = float2(rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_X), rngState.GetFloatSample(RAND_DIM_SURF_SCATTER_Y)); + + bounceRay = (UnifiedRT::Ray)0; + + float3 rayDirection; + if (!SampleDiffuseBrdf(u, hitGeom.worldFaceNormal, hitGeom.worldNormal, V, rayDirection, brdfPdf)) + return RAY_TERMINATION; + + bounceRay.origin = hitGeom.NextRayOrigin(); + bounceRay.direction = rayDirection; + bounceRay.tMin = 0; + bounceRay.tMax = K_T_MAX; + + return RAY_SCATTERING; +} + +uint RayMask(bool isFirstHitSurface) +{ + if (g_RenderedInstances == RENDER_ALL) + return DIRECT_RAY_VIS_MASK | INDIRECT_RAY_VIS_MASK; + else if (g_RenderedInstances == RENDER_ALL_IN_CAMERA_RAYS_THEN_ONLY_STATIC) + return isFirstHitSurface ? DIRECT_RAY_VIS_MASK | INDIRECT_RAY_VIS_MASK : INDIRECT_RAY_VIS_MASK; + else // RENDER_ONLY_STATIC + return isFirstHitSurface ? DIRECT_RAY_VIS_MASK : INDIRECT_RAY_VIS_MASK; +} + +uint ShadowRayMask() +{ + return SHADOW_RAY_VIS_MASK; +} + +float ComputeEmissiveTriangleDensity(StructuredBuffer instanceList, PTHitGeom hitGeom, int instanceIndex, float3 rayOrigin) +{ + float3 L = hitGeom.worldPosition - rayOrigin; + float d = length(L); + if (d > 0) + L *= rcp(d); + float cosTheta = dot(hitGeom.worldFaceNormal, L); + // pdf to sample this as area light + float weight = (d * d) / (hitGeom.triangleArea * cosTheta); + + // adjust pdf based on number of emissive triangles in the submesh that we hit + int geometryIndex = instanceList[instanceIndex].geometryIndex; + int numEmissiveTriangles = g_MeshList[geometryIndex].indexCount / uint(3); + weight /= numEmissiveTriangles; + + return weight; +} + +float ComputeMeshLightDensity(StructuredBuffer instanceList, PTHitGeom hitGeom, int instanceIndex, float3 rayOrigin) +{ + float weight = ComputeEmissiveTriangleDensity(instanceList, hitGeom, instanceIndex, rayOrigin); + + // pdf to select the light source + weight /= g_NumLights; + + return weight; +} + +bool ShouldTreatAsBackface(UnifiedRT::Hit hitResult, MaterialProperties material) +{ + // Have we hit something that is considered a backface when double sided GI is taken into account? + return !hitResult.isFrontFace && !material.doubleSidedGI; +} + +bool ShouldTreatAsBackface(bool isFrontFace, bool doubleSidedGI) +{ + return !isFrontFace && !doubleSidedGI; +} + +bool ShouldTransmitRay(inout PathTracingSampler rngState, MaterialProperties material) +{ + bool result = false; + if (material.isTransmissive) + { + // With proper support for IOR, the probability of refraction should be based on the materials fresnel. + // We don't have this information, so we base it on average transmission color, which matches the old baker. + // Additionally, we should divide the contribution of the ray by the probability of choosing either reflection or refraction, + // but we intentionally don't do this, since the old baker didn't do it either. + float transmissionProbability = dot(material.transmission, 1.0f) / 3.0f; + if (rngState.GetFloatSample(RAND_DIM_TRANSMISSION) < transmissionProbability) + { + result = true; + } + else + { + result = false; + } + } + return result; +} + +#define TRACE_MISS 0 +#define TRACE_HIT 1 +#define TRACE_TRANSMISSION 2 + +struct PathIterator +{ + UnifiedRT::Ray ray; + UnifiedRT::Hit hitResult; + PTHitGeom hitGeo; + MaterialProperties material; + float lastScatterProbabilityDensity; + float3 radianceSample; + float3 throughput; +}; + +void AddRadiance(inout float3 radianceAccumulator, float3 throughput, float3 radianceSample) +{ + radianceAccumulator += throughput * radianceSample; +} + +void InitPathIterator(out PathIterator iter, UnifiedRT::Ray primaryRay) +{ + iter = (PathIterator)0; + iter.lastScatterProbabilityDensity = 0; + iter.radianceSample = 0; + iter.throughput = 1; + iter.ray = primaryRay; +} + +uint TraceBounceRay(inout PathIterator iterator, int bounceIndex, uint rayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, inout PathTracingSampler rngState) +{ + bool isFirstRay = (bounceIndex == 0); + uint traceResult = TRACE_MISS; + + // Trace the ray. For primary rays in LiveGI we want to respect the backfacing culling properties of the material. Bounce rays follow the culling behavior of the baker. + int rayFlags = (bounceIndex == 0) ? UnifiedRT::kRayFlagCullBackFacingTriangles : UnifiedRT::kRayFlagNone; + iterator.hitResult = TraceRayClosestHit(dispatchInfo, accelStruct, rayMask, iterator.ray, rayFlags); + + iterator.hitGeo = (PTHitGeom) 0; + iterator.material = (MaterialProperties) 0; + UnifiedRT::InstanceData instanceInfo = (UnifiedRT::InstanceData) 0; + if (iterator.hitResult.IsValid()) + { + instanceInfo = UnifiedRT::GetInstance(iterator.hitResult.instanceID); + iterator.hitGeo = GetHitGeomInfo(instanceInfo, iterator.hitResult); + iterator.hitGeo.FixNormals(iterator.ray.direction); + + // Evaluate material properties at hit location + iterator.material = LoadMaterialProperties(instanceInfo, g_PathtracerAsGiPreviewMode && isFirstRay, iterator.hitGeo); + traceResult = TRACE_HIT; + } + else + { + traceResult = TRACE_MISS; + } + + if (traceResult == TRACE_HIT) + { + // If we hit a transmissive face, we should transmit or reflect. + if (ShouldTransmitRay(rngState, iterator.material)) + { + traceResult = TRACE_TRANSMISSION; + } + // We've hit a surface that should be treated as a backface, we should kill the ray instead of bouncing. This matches the old behavior. + else if (ShouldTreatAsBackface(iterator.hitResult, iterator.material)) + { + traceResult = TRACE_MISS; + } + } + + return traceResult; +} + +// Add radiance due to a missed ray which should sample the environment. +void AddEnvironmentRadiance(inout PathIterator iterator, bool applyIndirectScale) +{ + float3 envRadiance; + float envPdf; + if (GetEnvironmentLightEmissionAndDensity(iterator.ray.direction, envRadiance, envPdf)) + { + envPdf /= g_NumLights; + + if (applyIndirectScale) + envRadiance *= g_IndirectScale; + + AddRadiance(iterator.radianceSample, iterator.throughput, envRadiance * EmissiveMISWeightForBrdfRay(envPdf, iterator.lastScatterProbabilityDensity)); + } +} + +// Add radiance due to a hit emissive surface. +void AddEmissionRadiance(inout PathIterator iterator, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, bool applyIndirectScale) +{ + float lightDensity = ComputeMeshLightDensity(instanceList, iterator.hitGeo, iterator.hitResult.instanceID, iterator.ray.origin); + float3 emission = iterator.material.emissive; + if (applyIndirectScale) + emission *= g_IndirectScale; + AddRadiance(iterator.radianceSample, iterator.throughput, emission * EmissiveMISWeightForBrdfRay(lightDensity, iterator.lastScatterProbabilityDensity)); +} + +// Add radiance due to directly sampled lights. +void AddRadianceFromDirectIllumination( + inout PathIterator iterator, + uint shadowRayMask, + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct accelStruct, + StructuredBuffer instanceList, + inout PathTracingSampler rngState, + bool isDirect) +{ + const float3 radianceSample = EvalDirectIllumination( + dispatchInfo, accelStruct, instanceList, isDirect, shadowRayMask, iterator.hitGeo, iterator.material, min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS), rngState); + AddRadiance(iterator.radianceSample, iterator.throughput, radianceSample); +} + +// Add radiance due to directly sampled lights and randomly hit emissive surfaces. +void AddRadianceFromEmissionAndDirectIllumination(inout PathIterator iterator, int bounceIndex, uint shadowRayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, inout PathTracingSampler rngState) +{ + bool isFirstRay = (bounceIndex == 0); + + // Emission + if (!g_PathtracerAsGiPreviewMode || !isFirstRay) + { + AddEmissionRadiance(iterator, accelStruct, instanceList, !isFirstRay); + } + + // Check if we should do NEE, respecting the max bounce count + if (!g_CountNEERayAsPathSegment || bounceIndex < g_BounceCount) + { + AddRadianceFromDirectIllumination(iterator, shadowRayMask, dispatchInfo, accelStruct, instanceList, rngState, g_PathtracerAsGiPreviewMode && isFirstRay); + } +} + +// Trace the next path segment and accumulate radiance due to directly sampled lights, randomly hit emissive surfaces, and missed rays which sample the environment. +uint TraceBounceRayAndAddRadiance(inout PathIterator iterator, int bounceIndex, uint rayMask, uint shadowRayMask, UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList, inout PathTracingSampler rngState) +{ + uint traceResult = TraceBounceRay(iterator, bounceIndex, rayMask, dispatchInfo, accelStruct, rngState); + + if (traceResult == TRACE_HIT) + { + AddRadianceFromEmissionAndDirectIllumination(iterator, bounceIndex, shadowRayMask, dispatchInfo, accelStruct, instanceList, rngState); + } + else if (traceResult == TRACE_MISS) + { + AddEnvironmentRadiance(iterator, bounceIndex != 0); + } + + float bullet = rngState.GetFloatSample(RAND_DIM_RUSSIAN_ROULETTE); + + if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES) + { + float p = max(iterator.throughput.x, max(iterator.throughput.y, iterator.throughput.z)); + if (bullet > p) + traceResult = TRACE_MISS; + else + iterator.throughput /= p; + } + + return traceResult; +} + + +bool Scatter(inout PathIterator iterator, inout PathTracingSampler rngState) +{ + float brdfPdf; + int event = ScatterDiffusely(iterator.hitGeo, -iterator.ray.direction, rngState, iterator.ray, brdfPdf); + if (event == RAY_TERMINATION) + return false; + + // Here we assumes two things: + // 1) We use cosine distribution for bounce. + // 2) We never multiply the cosine density, cos(θ)/π. + // This cancels out the cosine term of the rendering equation and the division by π + // in the diffuse BRDF. Thus we only need to multiply by albedo below. + iterator.throughput *= iterator.material.baseColor; + + iterator.lastScatterProbabilityDensity = brdfPdf; + return true; +} + +float3 LoadMaterialTransmission(UnifiedRT::InstanceData instanceInfo, float2 uv0) +{ + int materialIndex = instanceInfo.userMaterialID; + PTMaterial matInfo = g_MaterialList[materialIndex]; + + bool pointSampleTransmission = (matInfo.flags & 4) != 0; + + return saturate(SampleAtlas(g_TransmissionTextures, sampler_g_TransmissionTextures, matInfo.transmissionTextureIndex, uv0, matInfo.transmissionScale, matInfo.transmissionOffset, pointSampleTransmission).rgb); +} + +uint AnyHitExecute(UnifiedRT::HitContext hitContext, inout PathTracingPayload payload) +{ + UnifiedRT::Hit hit; + hit.instanceID = hitContext.InstanceID(); + hit.primitiveIndex = hitContext.PrimitiveIndex(); + hit.uvBarycentrics = hitContext.UvBarycentrics(); + hit.hitDistance = hitContext.RayTCurrent(); + hit.isFrontFace = hitContext.IsFrontFace(); + + UnifiedRT::InstanceData instanceInfo = UnifiedRT::GetInstance(hitContext.InstanceID()); + UnifiedRT::HitGeomAttributes attributes = UnifiedRT::FetchHitGeomAttributes(hit); + float2 uv0 = attributes.uv0.xy; + + float3 transmission = LoadMaterialTransmission(instanceInfo, uv0); + + // With proper support for IOR, the probability of refraction should be based on the materials fresnel. + // We don't have this information, so we base it on average transmission color, which matches the old baker. + // Additionally, we should divide the contribution of the ray by the probability of choosing either reflection or refraction, + // but we intentionally don't do this, since the old baker didn't do it either. + + if (all(saturate(transmission) < 0.000001f)) + { + return UnifiedRT::kAcceptHit; + } + else + { + payload.SetTransmission(payload.GetTransmission() * saturate(transmission)); + return UnifiedRT::kIgnoreHit; + } +} + +void ClosestHitExecute(UnifiedRT::HitContext hitContext, inout PathTracingPayload payload) +{ + if (payload.IsShadowRay()) + { + payload.MarkHit(); + + } + else + { + UnifiedRT::Hit hit = (UnifiedRT::Hit)0; + hit.instanceID = hitContext.InstanceID(); + hit.primitiveIndex = hitContext.PrimitiveIndex(); + hit.uvBarycentrics = hitContext.UvBarycentrics(); + hit.isFrontFace = hitContext.IsFrontFace(); + hit.hitDistance = hitContext.RayTCurrent(); + payload.SetHit(hit); + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl.meta new file mode 100644 index 00000000000..68b2d33534f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ee19d1386b0b8ef47b4ab104ec460f81 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl new file mode 100644 index 00000000000..918c3b23fff --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl @@ -0,0 +1,201 @@ +#ifndef _PATHTRACING_PATHTRACINGCOMMON_HLSL_ +#define _PATHTRACING_PATHTRACINGCOMMON_HLSL_ + +#pragma warning(error: 3206) // Implicit truncation of vector type + +#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" + +#define LIGHT_SAMPLING 0 +#define BRDF_SAMPLING 1 +#define MIS 2 + +// Force uniform sampling of the skybox for debugging / ground truth generation +//#define UNIFORM_ENVSAMPLING + +// Emissive mesh sampling: we combine explicit light and brdf sampling using MIS. +// We can disable MIS and use exclusively one of the two sampling techniques with the EMISSIVE_SAMPLING define. +#ifndef EMISSIVE_SAMPLING +#define EMISSIVE_SAMPLING BRDF_SAMPLING +#endif + +#define SPOT_LIGHT 0 +#define DIRECTIONAL_LIGHT 1 +#define POINT_LIGHT 2 +#define RECTANGULAR_LIGHT 3 +#define DISC_LIGHT 4 +#define BOX_LIGHT 5 +#define EMISSIVE_MESH 8 // Must match the variable in UnityEngine.PathTracing.Core.World +#define ENVIRONMENT_LIGHT 9 // Must match the variable in UnityEngine.PathTracing.Core.World + +#define LIGHT_PICKING_METHOD_UNIFORM 0 +#define LIGHT_PICKING_METHOD_RESERVOIR_GRID 1 +#define LIGHT_PICKING_METHOD_LIGHT_GRID 2 + +#define DIRECT_RAY_VIS_MASK 1u +#define INDIRECT_RAY_VIS_MASK 2u +#define SHADOW_RAY_VIS_MASK 4u +// warning: bits 8u and 16u are used for lightmap LODs + +#define MAX_TRANSMISSION_BOUNCES 6 + + +struct HitEntry +{ + uint instanceID; + uint primitiveIndex; + float2 barycentrics; + bool IsValid() + { + return instanceID != -1; + } +}; + +struct PixelState +{ + float3 radiance; + float3 avgLightingDir; + float2 coord; + float2 motionVector; + float3 normal; + uint2 launchIndex; + uint2 launchDim; + float depth; + int bounces; +}; + +struct PTLight +{ + float3 position; + int type; + float3 intensity; + int castsShadows; + float3 forward; + int contributesToDirectLighting; + float4 attenuation; + float3 up; + float width; + float3 right; + float height; + uint layerMask; + float indirectScale; + float2 spotAngle; + float range; + int shadowMaskChannel; + int falloffIndex; + float shadowRadius; + int cookieIndex; +}; + +int GetMeshLightInstanceID(PTLight light) +{ + return light.height; +} + +float4 GetColumn(float4x4 mat, int colIndex) +{ + return float4(mat[0][3], mat[1][3], mat[2][3], mat[3][3]); +} + +// Photometric RGB -> (Perceived) Luminance / digital ITU BT.709 (https://www.itu.int/rec/R-REC-BT.709) +float Luminance(float3 color) +{ + const float3 weights = float3(0.2126f, 0.7152f, 0.0722f); + return dot(color, weights); +} + +float3 EvalDiffuseBrdf(float3 albedo) +{ + return albedo / PI; +} + +float ClampedCosine(float3 normal, float3 direction) +{ + return saturate(dot(normal, direction)); +} + +struct PTHitGeom +{ + float3 worldPosition; + float3 lastWorldPosition; + float3 worldNormal; + float3 worldFaceNormal; + float2 uv0; + float2 uv1; + float triangleArea; + uint renderingLayerMask; + + void FixNormals(float3 rayDirection) + { + worldFaceNormal = dot(rayDirection, worldFaceNormal) >= 0 ? -worldFaceNormal : worldFaceNormal; + worldNormal = dot(worldNormal, worldFaceNormal) < 0 ? -worldNormal : worldNormal; + } + + float3 NextRayOrigin() + { + return OffsetRayOrigin(worldPosition, worldFaceNormal); + } + + float3 NextTransmissionRayOrigin() + { + return OffsetRayOrigin(worldPosition, -worldFaceNormal); + } +}; + +PTHitGeom GetHitGeomInfo(UnifiedRT::InstanceData instanceInfo, UnifiedRT::Hit hit) +{ + UnifiedRT::HitGeomAttributes attributes = UnifiedRT::FetchHitGeomAttributes(hit); + + PTHitGeom res; + res.worldPosition = mul(instanceInfo.localToWorld, float4(attributes.position, 1)).xyz; + res.lastWorldPosition = mul(instanceInfo.previousLocalToWorld, float4(attributes.position, 1)).xyz; + res.worldNormal = normalize(mul((float3x3)instanceInfo.localToWorldNormals, attributes.normal)); + res.worldFaceNormal = mul((float3x3)instanceInfo.localToWorldNormals, attributes.faceNormal); + res.uv0 = attributes.uv0.xy; + res.uv1 = attributes.uv1.xy; + res.renderingLayerMask = instanceInfo.renderingLayerMask; + + // compute the area of the hit point (used for MIS) + float l = length(res.worldFaceNormal); + res.triangleArea = 0.5 * determinant(instanceInfo.localToWorld) * l; + + // normalize the face normal + if (l > 0) + res.worldFaceNormal /= l; + + return res; +} + +void FetchGeomAttributes(UnifiedRT::Hit hit, int geometryIndex, out float3 position, out float3 normal, out float3 faceNormal) +{ + position = normal = faceNormal = 0.0f; + // hit.instanceID is set to be the same as the submeshIndex + // geometryIndex always points to the first submesh, the others submeshes follow in order in the geometry array: + // submeshGeomIndex = geometryIndex + randomUVHit.instanceID + if (!hit.IsValid()) + return; + UnifiedRT::HitGeomAttributes hitAttribs = UnifiedRT::FetchHitGeomAttributes(geometryIndex + hit.instanceID, hit.primitiveIndex, hit.uvBarycentrics); + position = hitAttribs.position; + normal = hitAttribs.normal; + faceNormal = hitAttribs.faceNormal; +} + +void FetchGeomAttributes(UnifiedRT::Hit hit, int geometryIndex, out float3 position, out float3 normal, out float3 faceNormal, out float2 uv1) +{ + position = normal = faceNormal = 0.0f; + uv1 = 0.0f; + // hit.instanceID is set to be the same as the submeshIndex + // geometryIndex always points to the first submesh, the others submeshes follow in order in the geometry array: + // submeshGeomIndex = geometryIndex + randomUVHit.instanceID + if (!hit.IsValid()) + return; + UnifiedRT::HitGeomAttributes hitAttribs = UnifiedRT::FetchHitGeomAttributes(geometryIndex + hit.instanceID, hit.primitiveIndex, hit.uvBarycentrics); + position = hitAttribs.position; + normal = hitAttribs.normal; + faceNormal = hitAttribs.faceNormal; + uv1 = hitAttribs.uv1; +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl.meta new file mode 100644 index 00000000000..711a5882303 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingCommon.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5493fc629e26e32478e42b7007250ec4 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl new file mode 100644 index 00000000000..d04350802d9 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl @@ -0,0 +1,49 @@ +#ifndef _PATHTRACING_LIGHTGRID_HLSL_ +#define _PATHTRACING_LIGHTGRID_HLSL_ + +// Grid reservoirs per cell +uint g_NumReservoirs; + +// Grid dimentions +uint g_GridDimX; +uint g_GridDimY; +uint g_GridDimZ; + +// Grid Bounds +float4 g_GridMin; +float4 g_GridSize; + +// Cell dimentions +float4 g_CellSize; +float4 g_InvCellSize; + +struct ThinReservoir +{ + int lightIndex; + float weight; +}; + +float GetCellSize() +{ + return g_CellSize.w; +} + +float3 GetCellPosition(uint3 cellIndex) +{ + return g_GridMin.xyz + g_CellSize.xyz * (cellIndex + float3(0.5f, 0.5f, 0.5f)); +} + +uint GetCellIndex(uint3 cellCoord) +{ + uint slice = (g_GridDimX * g_GridDimY) * cellCoord.z; + return slice + cellCoord.y * g_GridDimX + cellCoord.x; +} + +uint GetCellIndexFromPosition(float3 pos) +{ + uint3 cellCoord = pos * g_InvCellSize.xyz - (g_GridMin.xyz * g_InvCellSize.xyz); + cellCoord = clamp(cellCoord, uint3(0, 0, 0), uint3(g_GridDimX, g_GridDimY, g_GridDimZ) - uint3(1, 1, 1)); + return GetCellIndex(cellCoord); +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl.meta new file mode 100644 index 00000000000..68c6d3943ca --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingLightGrid.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dded1b4d7a77fb940abbbb295be0acff +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl new file mode 100644 index 00000000000..7798f215896 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl @@ -0,0 +1,123 @@ +#ifndef _PATHTRACING_PATHTRACINGMATERIALS_HLSL_ +#define _PATHTRACING_PATHTRACINGMATERIALS_HLSL_ + +struct PTMaterial +{ + int albedoTextureIndex; + int emissionTextureIndex; + int transmissionTextureIndex; + uint flags; + float2 albedoScale; + float2 albedoOffset; + float2 emissionScale; + float2 emissionOffset; + float2 transmissionScale; + float2 transmissionOffset; + float3 emissionColor; + uint albedoAndEmissionUVChannel; +}; + +struct MaterialProperties +{ + float3 baseColor; + float metalness; + + float3 emissive; + float roughness; + + float3 transmission; + uint isTransmissive; + + uint doubleSidedGI; +}; + +StructuredBuffer g_MaterialList; + +// Textures +Texture2DArray g_AlbedoTextures; +Texture2DArray g_TransmissionTextures; +Texture2DArray g_EmissionTextures; +float g_AtlasTexelSize; // The size of 1 texel in the atlases above + +// Samplers +SamplerState sampler_g_EmissionTextures; +SamplerState sampler_g_AlbedoTextures; +SamplerState sampler_g_TransmissionTextures; + +float g_AlbedoBoost; + +float4 SampleAtlas(Texture2DArray atlas, SamplerState atlasSampler, uint index, float2 uv, float2 scale, float2 offset, bool pointFilterMode) +{ + // Apply the scale and offset to access to desired atlas entry + float2 localUV = uv * scale + offset; + + // To prevent sampling part of the neighbor due to bilinear filtering, we need to clamp the sample + // position to an 'inner rectangle' of the atlas entry, which is shrunk by half a texel on each side. + float2 innerRectMin = offset + (g_AtlasTexelSize * 0.5f); + // Calculating the rectangle extent is a bit tricky, because the size of the entry (scale) + // is not necessarily a multiple of the atlas texel size. We need to round it up to the next texel first, + // then subtract the half texel. + float2 innerRectMax = ceil((offset + scale) / g_AtlasTexelSize) * g_AtlasTexelSize - (g_AtlasTexelSize * 0.5f); + float2 clampedUV = clamp(localUV, innerRectMin, innerRectMax); + + [branch] if (pointFilterMode) + return atlas.Load(int4(clampedUV * rcp(g_AtlasTexelSize), index, 0)); + else + return atlas.SampleLevel(atlasSampler, float3(clampedUV, index), 0); +} + +MaterialProperties LoadMaterialProperties(UnifiedRT::InstanceData instanceInfo, bool useWhiteAsBaseColor, PTHitGeom hitGeom) +{ + int materialIndex = instanceInfo.userMaterialID; + PTMaterial matInfo = g_MaterialList[materialIndex]; + + MaterialProperties material = (MaterialProperties)0; + + material.baseColor = float3(0.75, 0.75, 0.75); + material.emissive = float3(0.0, 0.0, 0.0); + material.transmission = float3(1.0, 1.0, 1.0); + material.isTransmissive = matInfo.flags & 1; + + if (matInfo.albedoTextureIndex != -1) + { + float2 textureUV = matInfo.albedoAndEmissionUVChannel == 1 ? hitGeom.uv1 : hitGeom.uv0; + float4 texColor = SampleAtlas(g_AlbedoTextures, sampler_g_AlbedoTextures, matInfo.albedoTextureIndex, textureUV, matInfo.albedoScale, matInfo.albedoOffset, false); + material.baseColor = texColor.rgb; + // apply albedo boost, but still keep the reflectance at maximum 100% + material.baseColor = min(g_AlbedoBoost * material.baseColor, float3(1.0, 1.0, 1.0)); + material.transmission = 1.0f - float3(texColor.a, texColor.a, texColor.a); + } + + if (matInfo.emissionTextureIndex != -1) + { + float2 textureUV = matInfo.albedoAndEmissionUVChannel == 1 ? hitGeom.uv1 : hitGeom.uv0; + material.emissive = SampleAtlas(g_EmissionTextures, sampler_g_EmissionTextures, matInfo.emissionTextureIndex, textureUV, matInfo.emissionScale, matInfo.emissionOffset, false).rgb; + } + else + { + material.emissive = matInfo.emissionColor; + } + + if (matInfo.transmissionTextureIndex != -1) + { + float2 uv = hitGeom.uv0; + bool pointSampleTransmission = (matInfo.flags & 4) != 0; + material.transmission = saturate(SampleAtlas(g_TransmissionTextures, sampler_g_TransmissionTextures, matInfo.transmissionTextureIndex, uv, matInfo.transmissionScale, matInfo.transmissionOffset, pointSampleTransmission).rgb); + } + + // unused for now, we need these for specular support + material.roughness = 1.0; + material.metalness = 0; + + if (useWhiteAsBaseColor) + { + // override to white albedo, because albedo is multiplied with lightmap color at runtime + material.baseColor = float3(1, 1, 1); + } + + material.doubleSidedGI = matInfo.flags & 2; + + return material; +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl.meta new file mode 100644 index 00000000000..e19ab0de854 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingMaterials.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 584596a7dd3850f41ba28e8ac76d904a +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl new file mode 100644 index 00000000000..7601a837d85 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl @@ -0,0 +1,30 @@ + +// Blue noise sampling dimensions for various effects +#define RAND_DIM_AA_X 0 // used for jittering within a texel +#define RAND_DIM_AA_Y 1 +#define RAND_DIM_SURF_SCATTER_X 2 // used for determining the scattering direction when we have hit a surface +#define RAND_DIM_SURF_SCATTER_Y 3 +#define RAND_DIM_TRANSMISSION 4 // used to determine if a ray is transmitted or reflected +#define RAND_DIM_RUSSIAN_ROULETTE 5 // used for Russian roulette +#define RAND_DIM_JITTERED_SHADOW_X 6 // Used for jittered shadows, ideally should be removed and the jitter handled in the SampleLightShape code instead but that's a refactor for another day +#define RAND_DIM_JITTERED_SHADOW_Y 7 +#define RAND_DIM_LIGHT_SELECTION 8 // used for light selection (arbitrarily many dimensions after this point) + +#define RUSSIAN_ROULETTE_MIN_BOUNCES 2 // Min bounces for russian roulette. Matches PLM_DEFAULT_MIN_BOUNCES + +// Currently we use maximum 5 random numbers per light: +// 1 number to select a (mesh) light, +// 1 to select a triangle index, +// 2 numbers to sample the area/solid angle +// 1 number for RIS +// the total light samples need to be even, so we round to 6 +#define RAND_SAMPLES_PER_LIGHT 6 + +// Max number of lights to evaluate per bounce +#define MAX_LIGHT_EVALUATIONS 8 + +// The number of dimensions used per bounce (depends on the number of light evaluations) +#define QRNG_SAMPLES_PER_BOUNCE (RAND_DIM_LIGHT_SELECTION + RAND_SAMPLES_PER_LIGHT * MAX_LIGHT_EVALUATIONS) +#define QRNG_METHOD_SOBOL +#define QRNG_SOBOL_02 +#include "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/PathTracingSampler.hlsl" diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl.meta new file mode 100644 index 00000000000..49f453e6505 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingRandom.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 648103770f9a45649898dcd9d8aaa640 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl new file mode 100644 index 00000000000..f33ea713258 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl @@ -0,0 +1,107 @@ +#ifndef _PATHTRACING_PATHTRACINGSKYSAMPLING_HLSL_ +#define _PATHTRACING_PATHTRACINGSKYSAMPLING_HLSL_ + +#ifdef COMPUTE_PATH_TRACING_SKY_SAMPLING_DATA +RWStructuredBuffer _PathTracingSkyConditionalBuffer; +RWStructuredBuffer _PathTracingSkyMarginalBuffer; +#else +StructuredBuffer _PathTracingSkyConditionalBuffer; +StructuredBuffer _PathTracingSkyMarginalBuffer; +#endif + +uint _PathTracingSkyConditionalResolution; +uint _PathTracingSkyMarginalResolution; + +// Equiareal mapping +float2 MapSkyDirectionToUV(float3 dir) +{ + float cosTheta = dir.y; + float phi = atan2(-dir.z, -dir.x); + + return float2(0.5 - phi * INV_TWO_PI, (cosTheta + 1.0) * 0.5); +} + +// Equiareal mapping +float3 MapUVToSkyDirection(float u, float v) +{ + float phi = TWO_PI * (1.0 - u); + float cosTheta = 2.0 * v - 1.0; + + return TransformGLtoDX(SphericalToCartesian(phi, cosTheta)); +} + +float3 MapUVToSkyDirection(float2 uv) +{ + return MapUVToSkyDirection(uv.x, uv.y); +} + +#ifndef COMPUTE_PATH_TRACING_SKY_SAMPLING_DATA + + +bool IsSkySamplingEnabled() +{ + return _PathTracingSkyConditionalResolution; +} + +float GetSkyCDF(StructuredBuffer cdf, uint i, uint offset) +{ + return cdf[offset + i]; +} + +// Dichotomic search +float SampleSkyCDF(StructuredBuffer cdf, uint size, uint bufferOffset, float smp) +{ + uint i = 0; + for (uint halfsize = size >> 1, offset = halfsize; offset > 0; offset >>= 1) + { + if (smp < GetSkyCDF(cdf, halfsize, bufferOffset)) + { + // i is already in the right half (lower one) + halfsize -= offset >> 1; + } + else + { + // i has to move to the other half (upper one) + i += offset; + halfsize += offset >> 1; + } + } + + // MarginalTexture[0] stores the PDF normalization factor, so we need a test on i == 0 + float cdfInf = i > 0 ? GetSkyCDF(cdf, i, bufferOffset) : 0.0; + float cdfSup = i < size ? GetSkyCDF(cdf, i + 1, bufferOffset) : 1.0; + + return (i + (smp - cdfInf) / (cdfSup - cdfInf)) / size; +} + +float GetSkyPDFNormalizationFactor() +{ + return _PathTracingSkyMarginalBuffer[0]; +} + +// This PDF approximation is valid only if PDF/CDF tables are computed with equiareal mapping +float GetSkyPDFFromValue(float3 value, float pdfNormalization) +{ + return Luminance(value) * pdfNormalization; +} + +float GetSkyPDFFromValue(float3 value) +{ + return GetSkyPDFFromValue(value, GetSkyPDFNormalizationFactor()); +} + +float2 SampleSky(float smpU, float smpV) +{ + float v = SampleSkyCDF(_PathTracingSkyMarginalBuffer, _PathTracingSkyMarginalResolution, 0, smpV); + float u = SampleSkyCDF(_PathTracingSkyConditionalBuffer, _PathTracingSkyConditionalResolution, _PathTracingSkyConditionalResolution * uint(v * _PathTracingSkyMarginalResolution), smpU); + return float2(u, v); +} + +float2 SampleSky(float2 smp) +{ + return SampleSky(smp.x, smp.y); +} + +#endif // COMPUTE_PATH_TRACING_SKY_SAMPLING_DATA + +#endif // UNITY_PATH_TRACING_SKY_SAMPLING_INCLUDED diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl.meta new file mode 100644 index 00000000000..0129eb5a18e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySampling.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cd8fa354645948a46bc538729ae1b169 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute new file mode 100644 index 00000000000..4674ea29b13 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute @@ -0,0 +1,154 @@ +// This implementation is adapted from BuildProbabilityTables.compute + +#define COMPUTE_PATH_TRACING_SKY_SAMPLING_DATA + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl" +#include "PathTracingSkySampling.hlsl" + +#define KERNEL_WIDTH 64 + +// Make sky data access a little less verbose +#define CONDITIONAL _PathTracingSkyConditionalBuffer +#define MARGINAL _PathTracingSkyMarginalBuffer +#define CONDITIONAL_RESOLUTION _PathTracingSkyConditionalResolution +#define MARGINAL_RESOLUTION _PathTracingSkyMarginalResolution +TextureCube _PathTracingSkybox; +SamplerState sampler_PathTracingSkybox; + +float3 SampleSkyTexture(float3 dir, float lod, int sliceIndex) +{ + return _PathTracingSkybox.SampleLevel(sampler_PathTracingSkybox, dir, lod).xyz; +} + +// Performs a block-level parallel scan. +// Ref: GPU Gems 3, Chapter 39: "Parallel Prefix Sum (Scan) with CUDA". +void ParallelScan(uint i, uint w, uint iterCount, RWStructuredBuffer buf, uint bufferOffset, out float sum) +{ + uint offset; + + // Execute the up-sweep phase. + for (offset = 1; offset <= w / 2; offset *= 2) + { + AllMemoryBarrierWithGroupSync(); + + for (uint iter = 0; iter < iterCount; iter++) + { + uint idx = i + iter * KERNEL_WIDTH; + + // a1 = (2 * i + 1) * offset - 1 + uint a1 = Mad24(Mad24(2u, idx, 1u), offset, -1); + uint a2 = a1 + offset; + + if (a2 < w) + { + buf[bufferOffset + a2] += buf[bufferOffset + a1]; + } + } + } + + AllMemoryBarrierWithGroupSync(); + + // Prevent NaNs arising from the division of 0 by 0. + sum = max(buf[bufferOffset + w - 1], FLT_MIN); + + AllMemoryBarrierWithGroupSync(); + + // The exclusive scan requires the last element to be 0. + if (i == 0) + { + buf[bufferOffset + w - 1] = 0.0; + } + + // Execute the down-sweep phase. + for (offset = w / 2; offset > 0; offset /= 2) + { + AllMemoryBarrierWithGroupSync(); + + for (uint iter = 0; iter < iterCount; iter++) + { + uint idx = i + iter * KERNEL_WIDTH; + + // a1 = (2 * i + 1) * offset - 1 + uint a1 = Mad24(Mad24(2u, idx, 1u), offset, -1); + uint a2 = a1 + offset; + + if (a2 < w) + { + float t1 = buf[bufferOffset + a1]; + buf[bufferOffset + a1] = buf[bufferOffset + a2]; + buf[bufferOffset + a2] += t1; + } + } + } + + AllMemoryBarrierWithGroupSync(); +} + +#pragma kernel ComputeConditional +[numthreads(KERNEL_WIDTH, 1, 1)] +void ComputeConditional(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + const uint i = dispatchThreadId.x; + const uint j = dispatchThreadId.y; + const uint iterCount = CONDITIONAL_RESOLUTION / KERNEL_WIDTH; + const uint conditionalBufferOffset = j * CONDITIONAL_RESOLUTION; + + uint iter; + + float v = (j + 0.5) / MARGINAL_RESOLUTION; + //float sinTheta = sin(v * PI); + + for (iter = 0; iter < iterCount; iter++) + { + uint idx = i + iter * KERNEL_WIDTH; + float u = (idx + 0.5) / CONDITIONAL_RESOLUTION; + + // No need for a sinTheta term in the PDF when using equiareal mapping + float3 dir = MapUVToSkyDirection(u, v); + CONDITIONAL[conditionalBufferOffset + idx] = Luminance(SampleSkyTexture(dir, 0.0, 0).rgb); // * sinTheta; + } + + float rowValSum = 0.0f; + + ParallelScan(i, CONDITIONAL_RESOLUTION, iterCount, CONDITIONAL, conditionalBufferOffset, rowValSum); + + for (iter = 0; iter < iterCount; iter++) + { + uint idx = i + iter * KERNEL_WIDTH; + CONDITIONAL[conditionalBufferOffset + idx] /= rowValSum; + } + + if (i == 0) + { + float rowIntegralValue = rowValSum / CONDITIONAL_RESOLUTION; + MARGINAL[j] = rowIntegralValue; + } +} + +#pragma kernel ComputeMarginal + +[numthreads(KERNEL_WIDTH, 1, 1)] +void ComputeMarginal(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + const uint i = dispatchThreadId.x; + const uint iterCount = MARGINAL_RESOLUTION / KERNEL_WIDTH; + + float rowValSum = 0.0f; + + ParallelScan(i, MARGINAL_RESOLUTION, iterCount, MARGINAL, 0, rowValSum); + + for (uint iter = 0; iter < iterCount; iter++) + { + uint idx = i + iter * KERNEL_WIDTH; + MARGINAL[idx] /= rowValSum; + } + + if (i == 0) + { + float imgIntegralValue = rowValSum / MARGINAL_RESOLUTION; + float pdfNormalization = imgIntegralValue > 0.0 ? rcp(imgIntegralValue) * 0.25 * INV_PI : 0.0; + MARGINAL[0] = pdfNormalization; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute.meta new file mode 100644 index 00000000000..46b1ede85ac --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5bb2534d2411d344cbc54f880232640f +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader new file mode 100644 index 00000000000..065416eef04 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader @@ -0,0 +1,68 @@ +#define UNIFIED_RT_GROUP_SIZE_X 128 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC IntegrateDirectRadiance + +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" + +#include "PathTracing.hlsl" +#include "SphericalHarmonicsUtils.hlsl" + +RWStructuredBuffer g_Positions; +RWStructuredBuffer g_RadianceShl2; +uint g_PositionsOffset; +uint g_SampleOffset; +uint g_SampleCount; + +void IntegrateDirectRadiance(UnifiedRT::DispatchInfo dispatchInfo) +{ + const uint threadIdx = dispatchInfo.dispatchThreadID.x; + const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset; + const uint inProbeSampleIdx = threadIdx % g_SampleCount; + const uint outProbeIdx = threadIdx; + + PathTracingSampler rngState; + rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx); + + // Local array to accumulate radiance into, using SoA layout. + float3 accumulatedRadianceSH[SH_COEFFICIENTS_PER_CHANNEL]; + for (int i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i) + { + accumulatedRadianceSH[i] = 0.0f; + } + + // Set up some stuff we need to sample lights. + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + float3 worldPosition = g_Positions[inProbeIdx]; + + SampleLightsOptions options; + options.isDirect = true; + options.receiveShadows = true; + options.shadowRayMask = ShadowRayMask(); + options.lightsRenderingLayerMask = 0xFFFFFFFF; + options.numLightCandidates = min(g_LightEvaluations, MAX_LIGHT_EVALUATIONS); + + LightSample lightSample = (LightSample)0; + if (SampleLightsRadiance(dispatchInfo, accelStruct, g_AccelStructInstanceList, worldPosition, 0.0f, options, rngState, lightSample)) + { + // Project into SH. + accumulatedRadianceSH[0] += lightSample.radiance * SHL0(); + accumulatedRadianceSH[1] += lightSample.radiance * SHL1_1(lightSample.direction); + accumulatedRadianceSH[2] += lightSample.radiance * SHL10(lightSample.direction); + accumulatedRadianceSH[3] += lightSample.radiance * SHL11(lightSample.direction); + + accumulatedRadianceSH[4] += lightSample.radiance * SHL2_2(lightSample.direction); + accumulatedRadianceSH[5] += lightSample.radiance * SHL2_1(lightSample.direction); + accumulatedRadianceSH[6] += lightSample.radiance * SHL20(lightSample.direction); + accumulatedRadianceSH[7] += lightSample.radiance * SHL21(lightSample.direction); + accumulatedRadianceSH[8] += lightSample.radiance * SHL22(lightSample.direction); + } + + const float monteCarloNormalization = 1.0f / (float)g_SampleCount; + for (uint channel = 0; channel < SH_COLOR_CHANNELS; ++channel) + { + for (uint i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i) + { + g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = accumulatedRadianceSH[i][channel] * monteCarloNormalization; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader.meta new file mode 100644 index 00000000000..7db6dfb00d2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationDirect.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 1f9bfe60b4ad75943a5281839c1a4e5c +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader new file mode 100644 index 00000000000..222398e7965 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader @@ -0,0 +1,122 @@ +#define UNIFIED_RT_GROUP_SIZE_X 128 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC IntegrateIndirectRadiance + +#include "PathTracing.hlsl" +#include "SphericalHarmonicsUtils.hlsl" + +RWStructuredBuffer g_Positions; +RWStructuredBuffer g_RadianceShl2; +uint g_PositionsOffset; +uint g_SampleOffset; +uint g_SampleCount; + +float3 EstimateProbeRadiance(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::Ray ray, inout PathTracingSampler rngState) +{ + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + PathIterator pathIter; + InitPathIterator(pathIter, ray); + + int transparencyBounce = 0; + // We start at bounce index 1, as bounce index is defined relative to the camera for this path tracer. + // Since this function is used for baking, we already implicitly have the first "hit", and are about + // to process the second path segment. + for (int bounceIndex = 1; bounceIndex <= g_BounceCount && transparencyBounce < MAX_TRANSMISSION_BOUNCES; bounceIndex++) + { + // The first path segment is special for the indirect pass - we should not add radiance from the + // environment or emission, as these are already explicitly sampled in the direct pass. + bool isFirstPathSegment = bounceIndex == 1; + + uint pathRayMask = RayMask(bounceIndex == 0); + uint shadowRayMask = ShadowRayMask(); + + uint traceResult = TraceBounceRay(pathIter, bounceIndex, pathRayMask, dispatchInfo, accelStruct, rngState); + + if (traceResult == TRACE_HIT) + { + if (!isFirstPathSegment) + AddEmissionRadiance(pathIter, accelStruct, g_AccelStructInstanceList, false); + AddRadianceFromDirectIllumination(pathIter, shadowRayMask, dispatchInfo, accelStruct, g_AccelStructInstanceList, rngState, false); + } + + if (traceResult == TRACE_MISS) + { + if (!isFirstPathSegment) + AddEnvironmentRadiance(pathIter, false); + break; + } + + if (traceResult == TRACE_TRANSMISSION) + { + bounceIndex--; + transparencyBounce++; + pathIter.ray.origin = pathIter.hitGeo.NextTransmissionRayOrigin(); + pathIter.throughput *= pathIter.material.transmission; + rngState.NextBounce(); + continue; + } + + if (!Scatter(pathIter, rngState)) + break; + + if (bounceIndex >= RUSSIAN_ROULETTE_MIN_BOUNCES) + { + float p = max(pathIter.throughput.x, max(pathIter.throughput.y, pathIter.throughput.z)); + if (rngState.GetFloatSample(RAND_DIM_RUSSIAN_ROULETTE) > p) + break; + else + pathIter.throughput /= p; + } + + rngState.NextBounce(); + } + + return pathIter.radianceSample; +} + +void IntegrateIndirectRadiance(UnifiedRT::DispatchInfo dispatchInfo) +{ + const uint threadIdx = dispatchInfo.dispatchThreadID.x; + const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset; + const uint inProbeSampleIdx = threadIdx % g_SampleCount; + const uint outProbeIdx = threadIdx; + + PathTracingSampler rngState; + rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx); + + // TODO(pema.malling): This works but that is sort of by accident. Avoid coupling to AA (which is unrelated to probe integration). https://jira.unity3d.com/browse/LIGHT-1687 + const float3 uniformSphereDir = MapSquareToSphere(float2(rngState.GetFloatSample(RAND_DIM_AA_X), rngState.GetFloatSample(RAND_DIM_AA_Y))); + + UnifiedRT::Ray ray; + ray.origin = g_Positions[inProbeIdx]; + ray.direction = uniformSphereDir; + ray.tMin = 0; + ray.tMax = K_T_MAX; + + float3 radiance = EstimateProbeRadiance(dispatchInfo, ray, rngState); + + // Local array to accumulate radiance into, using SoA layout. + float3 accumulatedRadianceSH[SH_COEFFICIENTS_PER_CHANNEL]; + accumulatedRadianceSH[0] = radiance * SHL0(); + accumulatedRadianceSH[1] = radiance * SHL1_1(uniformSphereDir); + accumulatedRadianceSH[2] = radiance * SHL10(uniformSphereDir); + accumulatedRadianceSH[3] = radiance * SHL11(uniformSphereDir); + + accumulatedRadianceSH[4] = radiance * SHL2_2(uniformSphereDir); + accumulatedRadianceSH[5] = radiance * SHL2_1(uniformSphereDir); + accumulatedRadianceSH[6] = radiance * SHL20(uniformSphereDir); + accumulatedRadianceSH[7] = radiance * SHL21(uniformSphereDir); + accumulatedRadianceSH[8] = radiance * SHL22(uniformSphereDir); + + const float reciprocalSampleCount = 1.0f / (float) g_SampleCount; + const float reciprocalUniformSphereDensity = 4.0f * PI; + const float monteCarloNormalization = reciprocalSampleCount * reciprocalUniformSphereDensity; + for (uint channel = 0; channel < SH_COLOR_CHANNELS; ++channel) + { + for (uint i = 0; i < SH_COEFFICIENTS_PER_CHANNEL; ++i) + { + g_RadianceShl2[SHIndex(outProbeIdx, channel, i)] = accumulatedRadianceSH[i][channel] * monteCarloNormalization; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader.meta new file mode 100644 index 00000000000..1fd42c8a6b7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationIndirect.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 5ecdabd800f05984696f22ba95284516 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader new file mode 100644 index 00000000000..78e1a3237d2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader @@ -0,0 +1,65 @@ +#define UNIFIED_RT_GROUP_SIZE_X 128 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC IntegrateOcclusion + +#include "PathTracing.hlsl" +#include "LightSampling.hlsl" + +RWStructuredBuffer g_Positions; +RWStructuredBuffer g_PerProbeLightIndices; +RWStructuredBuffer g_Occlusion; +uint g_ExpansionOffset; +uint g_PositionsOffset; +uint g_PerProbeLightIndicesOffset; +uint g_MaxLightsPerProbe; +uint g_SampleCount; +uint g_SampleOffset; + +void IntegrateOcclusion(UnifiedRT::DispatchInfo dispatchInfo) +{ + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + + const uint threadIdx = dispatchInfo.dispatchThreadID.x; + const uint inProbeIdx = threadIdx / g_SampleCount; + const uint inProbeIdxWithOffset = inProbeIdx + g_PositionsOffset; + const uint inProbeSampleIdx = threadIdx % g_SampleCount; + + float3 worldPosition = g_Positions[inProbeIdxWithOffset]; + + // Which probe in the expanded g_Occlusion buffer are we in? + const uint outProbeIdx = inProbeIdx * g_SampleCount + inProbeSampleIdx; + + for (uint indirectLightIndex = 0; indirectLightIndex < g_MaxLightsPerProbe; indirectLightIndex++) + { + // Which light in the expanded g_Occlusion buffer are we in? + const uint outOcclusionValueIdx = outProbeIdx * g_MaxLightsPerProbe + indirectLightIndex; + // Which light in the non-expanded g_PerProbeLightIndices buffer are we in? + const uint perProbeLightIndicesIdx = (inProbeIdx + g_ExpansionOffset) * g_MaxLightsPerProbe + indirectLightIndex + g_PerProbeLightIndicesOffset; + + PathTracingSampler rngState; + rngState.Init(inProbeIdxWithOffset, g_SampleOffset + inProbeSampleIdx); + + uint lightIndex = g_PerProbeLightIndices[perProbeLightIndicesIdx]; + if (lightIndex == -1) + { + g_Occlusion[outOcclusionValueIdx] = 0.0f; + continue; + } + + PTLight light = FetchLight(lightIndex); + if (light.type != SPOT_LIGHT && light.type != POINT_LIGHT && light.type != DIRECTIONAL_LIGHT) + { + g_Occlusion[outOcclusionValueIdx] = 0.0f; + continue; + } + + uint dimsOffset = 0; + uint dimsUsed = 0; + float3 attenuation = 1.0f; + bool isVisible = IsLightVisibleFromPoint(dispatchInfo, accelStruct, g_AccelStructInstanceList, SHADOW_RAY_VIS_MASK, worldPosition, rngState, dimsOffset, dimsUsed, light, true, attenuation); + if (isVisible) + { + g_Occlusion[outOcclusionValueIdx] = 1.0f / g_SampleCount; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader.meta new file mode 100644 index 00000000000..96617464b71 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationOcclusion.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: de3013e16d99f2746ab1735ec06a660b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader new file mode 100644 index 00000000000..1b255a81d5c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader @@ -0,0 +1,56 @@ +#define UNIFIED_RT_GROUP_SIZE_X 128 +#define UNIFIED_RT_GROUP_SIZE_Y 1 +#define UNIFIED_RT_RAYGEN_FUNC IntegrateValidity + +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRayAndQueryHit.hlsl" + +#include "PathTracingCommon.hlsl" +#include "PathTracingMaterials.hlsl" +#include "PathTracingRandom.hlsl" + +UNIFIED_RT_DECLARE_ACCEL_STRUCT(g_SceneAccelStruct); + +RWStructuredBuffer g_Positions; +RWStructuredBuffer g_Validity; +uint g_PositionsOffset; +uint g_SampleCount; +uint g_SampleOffset; + +void IntegrateValidity(UnifiedRT::DispatchInfo dispatchInfo) +{ + const uint threadIdx = dispatchInfo.dispatchThreadID.x; + const uint inProbeIdx = threadIdx / g_SampleCount + g_PositionsOffset; + const uint inProbeSampleIdx = threadIdx % g_SampleCount; + const uint outProbeIdx = threadIdx; + + PathTracingSampler rngState; + rngState.Init(inProbeIdx, g_SampleOffset + inProbeSampleIdx); + + // TODO(pema.malling): This works but that is sort of by accident. Avoid coupling to AA (which is unrelated to probe integration). https://jira.unity3d.com/browse/LIGHT-1687 + const float3 uniformSphereDir = MapSquareToSphere(float2(rngState.GetFloatSample(RAND_DIM_AA_X), rngState.GetFloatSample(RAND_DIM_AA_Y))); + + UnifiedRT::Ray ray; + ray.origin = g_Positions[inProbeIdx]; + ray.direction = uniformSphereDir; + ray.tMin = 0; + ray.tMax = K_T_MAX; + + int rayFlags = UnifiedRT::kRayFlagNone; + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + UnifiedRT::Hit hitResult = UnifiedRT::TraceRayClosestHit(dispatchInfo, accelStruct, UINT_MAX, ray, rayFlags); + + bool invalidHit = false; + if (hitResult.IsValid()) + { + UnifiedRT::InstanceData instance = UnifiedRT::GetInstance(hitResult.instanceID); + PTHitGeom geometry = GetHitGeomInfo(instance, hitResult); + MaterialProperties material = LoadMaterialProperties(instance, false, geometry); + + if (!hitResult.isFrontFace && !material.doubleSidedGI && !material.isTransmissive) + invalidHit = true; + } + + g_Validity[outProbeIdx] = invalidHit ? 1.0f / g_SampleCount : 0.0f; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader.meta new file mode 100644 index 00000000000..94d9fb1810a --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeIntegrationValidity.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 2c32233d92a2d0745930664c087c5c9a +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute new file mode 100644 index 00000000000..84f66596901 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute @@ -0,0 +1,26 @@ +// See UnityComputeProbeIntegrator.IntegrateOcclusion() +#pragma kernel MapIndices + +RWStructuredBuffer g_PerProbeLightIndicesOutput; +StructuredBuffer g_PerProbeLightIndicesInput; +StructuredBuffer g_MappingTable; +uint g_PerProbeLightIndicesInputOffset; +uint g_MaxLightsPerProbe; +uint g_ProbeCount; + +[numthreads(64,1,1)] +void MapIndices(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= g_MaxLightsPerProbe * g_ProbeCount) + return; + + for (uint i = 0; i < g_MaxLightsPerProbe; i++) + { + uint probeIdx = id.x * g_MaxLightsPerProbe + i; + int inLightIdx = g_PerProbeLightIndicesInput[probeIdx + g_PerProbeLightIndicesInputOffset]; + if (inLightIdx < 0) + g_PerProbeLightIndicesOutput[probeIdx] = -1; + else + g_PerProbeLightIndicesOutput[probeIdx] = g_MappingTable[inLightIdx]; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute.meta new file mode 100644 index 00000000000..314299210fd --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbeOcclusionLightIndexMapping.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b69d394493d90648bb31414187ae4db +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute new file mode 100644 index 00000000000..e163dc416cb --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute @@ -0,0 +1,166 @@ +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" + +#define UNIFIED_RT_BACKEND_COMPUTE +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/CommonStructs.hlsl" +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/FetchGeometry.hlsl" + +#include "PathTracingCommon.hlsl" +#include "SphericalHarmonicsUtils.hlsl" + +#pragma kernel ConvolveRadianceToIrradiance +#pragma kernel ConvertToUnityFormat +#pragma kernel AddSphericalHarmonicsL2 +#pragma kernel ScaleSphericalHarmonicsL2 +#pragma kernel WindowSphericalHarmonicsL2 + +StructuredBuffer g_PrimaryInputShl2; +StructuredBuffer g_SecondaryInputShl2; +RWStructuredBuffer g_OutputShl2; +uint g_PrimaryInputOffset; +uint g_SecondaryInputOffset; +uint g_OutputOffset; +uint g_ProbeCount; +float g_Scale; + +[numthreads(64, 1, 1)] +void ConvolveRadianceToIrradiance(in uint3 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= g_ProbeCount) + return; + + uint inProbeIdx = gidx.x + g_PrimaryInputOffset; + uint outProbeIdx = gidx.x + g_OutputOffset; + + // aHat is from https://cseweb.ucsd.edu/~ravir/papers/envmap/envmap.pdf and is used + // to convert spherical radiance to irradiance. + float aHat0 = 3.1415926535897932384626433832795028841971693993751058209749445923f; // π + float aHat1 = 2.0943951023931954923084289221863352561314462662500705473166297282f; // 2π/3 + float aHat2 = 0.7853981633974483096156608458198757210492923498437764552437361480f; // π/4 + for (uint channel = 0; channel < SH_COLOR_CHANNELS; channel++) + { + g_OutputShl2[SHIndex(outProbeIdx, channel, 0)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, 0)] * aHat0; + + uint i = 0; + for (i = 1; i < 4; ++i) + { + g_OutputShl2[SHIndex(outProbeIdx, channel, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, i)] * aHat1; + } + + for (i = 4; i < 9; ++i) + { + g_OutputShl2[SHIndex(outProbeIdx, channel, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, channel, i)] * aHat2; + } + } +} + +[numthreads(64, 1, 1)] +void ConvertToUnityFormat(in uint3 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= g_ProbeCount) + return; + + uint inProbeIdx = gidx.x + g_PrimaryInputOffset; + uint outProbeIdx = gidx.x + g_OutputOffset; + + for (int rgb = 0; rgb < SH_COLOR_CHANNELS; ++rgb) + { + // Copy input SH so we don't overwrite the inputs while we're reading them, + // in case the input and output buffers are aliased. + const uint baseInputIdx = SHIndex(inProbeIdx, rgb, 0); + float l00 = g_PrimaryInputShl2[baseInputIdx]; + float l1_1 = g_PrimaryInputShl2[baseInputIdx + 1]; + float l10 = g_PrimaryInputShl2[baseInputIdx + 2]; + float l11 = g_PrimaryInputShl2[baseInputIdx + 3]; + float l2_2 = g_PrimaryInputShl2[baseInputIdx + 4]; + float l2_1 = g_PrimaryInputShl2[baseInputIdx + 5]; + float l20 = g_PrimaryInputShl2[baseInputIdx + 6]; + float l21 = g_PrimaryInputShl2[baseInputIdx + 7]; + float l22 = g_PrimaryInputShl2[baseInputIdx + 8]; + + // Calculate output indices + const uint l00Index = SHIndex(outProbeIdx, rgb, 0); + const uint l1_1Index = l00Index + 1; + const uint l10Index = l00Index + 2; + const uint l11Index = l00Index + 3; + const uint l2_2Index = l00Index + 4; + const uint l2_1Index = l00Index + 5; + const uint l20Index = l00Index + 6; + const uint l21Index = l00Index + 7; + const uint l22Index = l00Index + 8; + + // L0 + g_OutputShl2[l00Index] = (l00 * SH_L0_NORMALIZATION) / PI; + + // L1 + g_OutputShl2[l1_1Index] = (l10 * SH_L1_NORMALIZATION) / PI; + g_OutputShl2[l10Index] = (l11 * SH_L1_NORMALIZATION) / PI; + g_OutputShl2[l11Index] = (l1_1 * SH_L1_NORMALIZATION) / PI; + + // L2 + g_OutputShl2[l2_2Index] = (l2_2 * SH_L2_2_NORMALIZATION) / PI; + g_OutputShl2[l2_1Index] = (l2_1 * SH_L2_1_NORMALIZATION) / PI; + g_OutputShl2[l20Index] = (l20 * SH_L20_NORMALIZATION) / PI; + g_OutputShl2[l21Index] = (l21 * SH_L21_NORMALIZATION) / PI; + g_OutputShl2[l22Index] = (l22 * SH_L22_NORMALIZATION) / PI; + } +} + +[numthreads(64, 1, 1)] +void AddSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= g_ProbeCount) + return; + + uint inProbeIdx = gidx.x + g_PrimaryInputOffset; + uint inSecondaryProbeIdx = gidx.x + g_SecondaryInputOffset; + uint outProbeIdx = gidx.x + g_OutputOffset; + + for (uint i = 0; i < SH_TOTAL_COEFFICIENTS; i++) + { + g_OutputShl2[SHIndex(outProbeIdx, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, i)] + g_SecondaryInputShl2[SHIndex(inSecondaryProbeIdx, i)]; + } +} + +[numthreads(64, 1, 1)] +void ScaleSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= g_ProbeCount) + return; + + uint inProbeIdx = gidx.x + g_PrimaryInputOffset; + uint outProbeIdx = gidx.x + g_OutputOffset; + + for (uint i = 0; i < SH_TOTAL_COEFFICIENTS; i++) + { + g_OutputShl2[SHIndex(outProbeIdx, i)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, i)] * g_Scale; + } +} + +[numthreads(64, 1, 1)] +void WindowSphericalHarmonicsL2(in uint3 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= g_ProbeCount) + return; + + uint inProbeIdx = gidx.x + g_PrimaryInputOffset; + uint outProbeIdx = gidx.x + g_OutputOffset; + + // Windowing constants from WindowDirectSH in SHDering.cpp. + const float extraWindow[3] = { 1.0f, 0.922066f, 0.731864f }; + + // Apply windowing: Essentially SHConv3 times the window constants. + for (uint coefficient = 0; coefficient < SH_COEFFICIENTS_PER_CHANNEL; coefficient++) + { + float window; + if (coefficient == 0) // DC + window = extraWindow[0]; + else if (coefficient < 4) // L1 + window = extraWindow[1]; + else + window = extraWindow[2]; // L2 + + g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_RED, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_RED, coefficient)] * window; + g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_GREEN, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_GREEN, coefficient)] * window; + g_OutputShl2[SHIndex(outProbeIdx, SH_CHANNEL_BLUE, coefficient)] = g_PrimaryInputShl2[SHIndex(inProbeIdx, SH_CHANNEL_BLUE, coefficient)] * window; + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute.meta new file mode 100644 index 00000000000..a6e6ea75277 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ProbePostProcessing.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 592b8faa1d5c42841bcb3308316baf9e +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute new file mode 100644 index 00000000000..034b8e734f7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute @@ -0,0 +1,96 @@ +RWTexture2D g_LightmapInOut; +int g_TextureWidth; +int g_TextureHeight; + +RWTexture2D g_DirectionalInOut; +Texture2D g_InputSampleCountInW; +Texture2D g_Normals; + +#pragma kernel NormalizeValidity +[numthreads(8, 8, 1)] +void NormalizeValidity( + in uint3 gidx : SV_DispatchThreadID, + in uint lidx : SV_GroupIndex) +{ + if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1) + return; + + const float4 validity = g_LightmapInOut[gidx.xy]; + const float sampleCount = max(validity.w, 1.0f); + const float4 normalizedValidity = float4(validity.xyz * (1.f / sampleCount), 1.f); + g_LightmapInOut[gidx.xy] = validity.w > 0.0f ? normalizedValidity : 0.0f; +} + +#pragma kernel NormalizeAO +[numthreads(8, 8, 1)] +void NormalizeAO( + in uint3 gidx : SV_DispatchThreadID, + in uint lidx : SV_GroupIndex) +{ + if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1) + return; + + const float4 occlusion = g_LightmapInOut[gidx.xy]; + const float4 vZero = 0.0f; + const float sampleCount = max(occlusion.w, 1.0f); + const float4 normalizedOcclusion = float4(1.f - (occlusion.xyz * (1.f / sampleCount)), 1.f); + g_LightmapInOut[gidx.xy] = occlusion.w > 0.0f ? normalizedOcclusion : vZero; +} + +#pragma kernel NormalizeRadiance +[numthreads(8, 8, 1)] +void NormalizeRadiance( + in uint3 gidx : SV_DispatchThreadID, + in uint lidx : SV_GroupIndex) +{ + if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1) + return; + + const float4 radiance = g_LightmapInOut[gidx.xy]; + const float4 vZero = 0.0f; + const float sampleCount = max(radiance.w, 1.0f); + const float4 normalizedRadiance = float4(radiance.xyz * (1.f / sampleCount), 1.f); + g_LightmapInOut[gidx.xy] = radiance.w > 0.0f ? normalizedRadiance : vZero; +} + +#pragma kernel NormalizeDirection +[numthreads(8, 8, 1)] +void NormalizeDirection( + in uint3 gidx : SV_DispatchThreadID, + in uint lidx : SV_GroupIndex) +{ + if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1) + return; + + const float4 vHalf = 0.5f; + const bool valid = g_InputSampleCountInW[gidx.xy].w > 0.0f; + if (!valid) + { + g_DirectionalInOut[gidx.xy] = vHalf; + return; + } + + const float4 epsilon = 0.001f; + const float3 normal = g_Normals[gidx.xy].xyz; + + float4 directionality = g_DirectionalInOut[gidx.xy]; + directionality *= 1.0f / max(epsilon, directionality.wwww); + directionality.w = dot(normal, directionality.xyz); + g_DirectionalInOut[gidx.xy] = directionality * vHalf + vHalf; +} + +#pragma kernel NormalizeShadowMask +[numthreads(8, 8, 1)] +void NormalizeShadowMask( + in uint3 gidx : SV_DispatchThreadID, + in uint lidx : SV_GroupIndex) +{ + if (gidx.x >= (uint) g_TextureWidth || gidx.y >= (uint) g_TextureHeight || gidx.z >= 1) + return; + + const float4 shadow = g_LightmapInOut[gidx.xy]; + const float4 vZero = 0.0f; + const float sampleCount = max(g_InputSampleCountInW[gidx.xy].w, 1.0f); + const float4 normalizedShadow = float4(shadow * (1.f / sampleCount)); + g_LightmapInOut[gidx.xy] = g_InputSampleCountInW[gidx.xy].w > 0.0f ? normalizedShadow : vZero; +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute.meta new file mode 100644 index 00000000000..6b4bf5297af --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/ResolveAccumulation.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d9225443dcd40974c9a13cb6e166ca5f +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute new file mode 100644 index 00000000000..04572c8e319 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute @@ -0,0 +1,87 @@ +// This shader computes a strided segmented reduction - essentially N reductions in parallel, in a single pass. +// A very simple approach is used, where each thread just computes the i'th reduction sequentially. +// Each segment is assumed to be the same length. The shader adds to its output, so zero-initialize it if needed. + +// Example with width = 2, stride = 3: +// +// Input buffer layout: +// |val 0|val 1|val 2|val 3|val 4|val 5|val 6|val 7|val 8|val 9|val 10|val 11|... +// | element 0 | element 1 | element 2 | element 3 |... +// | segment 0 | segment 1 |... +// +// Resulting output buffer: +// |val 0 + val 3|val 1 + val 4|val 2 + val 5|val 6 + val 9|val 7 + val 10|val 8 + val 11|... +// | element 0 + element 1 | element 2 + element 3 |... + +#pragma kernel SegmentedReductionFloat + +#define MAX_SEGMENT_STRIDE 32 + +uint g_SegmentWidth; // How many elements in each segment +uint g_SegmentStride; // How many values in each element +uint g_SegmentCount; // How many segments +uint g_InputOffset; // Offset into input buffer, specified in elements +uint g_OutputOffset; // Offset into output buffer, specified in elements +uint g_OverwriteOutput; // If 1, overwrite the output buffer. Otherwise, add to it. +StructuredBuffer g_InputFloatBuffer; // Length = g_SegmentWidth * g_SegmentStride * g_NumSegments +RWStructuredBuffer g_OutputFloatBuffer; // Length = g_SegmentStride * g_NumSegments + +// These uniforms can be used for multi-pass reductions, handling cases where the input segments can +// not be cleanly divided into sub-segments. If g_TruncateInterval > 0 && g_TruncatedSegmentWidth > 0, +// every g_TruncateInterval'th segment will be truncated to a width given by g_TruncatedSegmentWidth. +uint g_TruncateInterval; +uint g_TruncatedSegmentWidth; + +[numthreads(64,1,1)] +void SegmentedReductionFloat(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= g_SegmentCount) + return; + + uint currentSegmentWidth = g_SegmentWidth; // How large is this threads segment? + uint truncatedElementsThusFar = 0; // How many elements have been truncated thus far? + + // Handle truncation + bool truncationEnabled = g_TruncateInterval > 0 && g_TruncatedSegmentWidth > 0; + if (truncationEnabled) + { + truncatedElementsThusFar = (id.x / g_TruncateInterval) * (g_SegmentWidth - g_TruncatedSegmentWidth); + + bool shouldTruncate = (id.x + 1) % g_TruncateInterval == 0; + if (shouldTruncate) + { + currentSegmentWidth = g_TruncatedSegmentWidth; + } + } + + // Step 1: Zero initialize an accumulator in local memory + float accumulator[MAX_SEGMENT_STRIDE]; + uint valueIdx; + for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++) + { + accumulator[valueIdx] = 0.0f; + } + + // Step 2: Accumulate values in the segment into local memory + const uint baseElementIndex = id.x * g_SegmentWidth - truncatedElementsThusFar + g_InputOffset; + for (uint elementIdx = 0; elementIdx < currentSegmentWidth; elementIdx++) + { + for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++) + { + accumulator[valueIdx] += g_InputFloatBuffer[(baseElementIndex + elementIdx) * g_SegmentStride + valueIdx]; + } + } + + // Step 3: Write accumulated values to global memory + for (valueIdx = 0; valueIdx < g_SegmentStride; valueIdx++) + { + if (g_OverwriteOutput) + { + g_OutputFloatBuffer[(id.x + g_OutputOffset) * g_SegmentStride + valueIdx] = accumulator[valueIdx]; + } + else + { + g_OutputFloatBuffer[(id.x + g_OutputOffset) * g_SegmentStride + valueIdx] += accumulator[valueIdx]; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute.meta new file mode 100644 index 00000000000..679c86c38af --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SegmentedReduction.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 065a0fb9495518147aaf6f482d53ff42 +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute new file mode 100644 index 00000000000..a9a459a00c2 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute @@ -0,0 +1,45 @@ +#define GROUP_SIZE_X 8 +#define GROUP_SIZE_Y 8 + +int g_TextureWidth; +int g_TextureHeight; +int g_TargetSlice; +int g_TargetOffsetX; +int g_TargetOffsetY; +Texture2D g_OpacityTexture; +SamplerState sampler_g_OpacityTexture; +float g_Alpha; +uint g_UseAlphaCutoff; +float g_AlphaCutoff; +float4 g_OpacityTextureUVTransform; + +RWTexture2DArray g_AlbedoTextures; + +#pragma kernel SetAlphaChannel +[numthreads(GROUP_SIZE_X, GROUP_SIZE_Y, 1)] +void SetAlphaChannel(in uint2 gidx: SV_DispatchThreadID) +{ + if (gidx.x >= uint(g_TextureWidth) || gidx.y >= uint(g_TextureHeight)) + return; + + uint2 targetOffset = uint2(g_TargetOffsetX, g_TargetOffsetY); + + float2 uv = (float2(gidx)+0.5) / float2(g_TextureWidth, g_TextureHeight); + uv = g_OpacityTextureUVTransform.xy * uv + g_OpacityTextureUVTransform.zw; + + float2 dUvdx = g_OpacityTextureUVTransform.x / float(g_TextureWidth); + float2 dUvdy = g_OpacityTextureUVTransform.y / float(g_TextureHeight); + + float4 texelValue = g_AlbedoTextures[uint3(gidx + targetOffset, g_TargetSlice)]; + + texelValue.a = g_OpacityTexture.SampleGrad(sampler_g_OpacityTexture, uv, dUvdx, dUvdy).a * g_Alpha; + + // Alpha clipping + if (g_UseAlphaCutoff) + { + texelValue.a = (texelValue.a > g_AlphaCutoff) ? 1.0f : 0.0f; + } + + g_AlbedoTextures[uint3(gidx + targetOffset, g_TargetSlice)] = texelValue; +} + diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute.meta new file mode 100644 index 00000000000..521fc5e0e87 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SetAlphaChannel.compute.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5efaea0e81c66334aa9d062d6573e6fd +ComputeShaderImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl new file mode 100644 index 00000000000..11aba8ef3af --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl @@ -0,0 +1,88 @@ +#define SH_COLOR_CHANNELS 3 +#define SH_COEFFICIENTS_PER_CHANNEL 9 +#define SH_TOTAL_COEFFICIENTS (SH_COLOR_CHANNELS * SH_COEFFICIENTS_PER_CHANNEL) + +#define SH_CHANNEL_RED 0 +#define SH_CHANNEL_GREEN 1 +#define SH_CHANNEL_BLUE 2 + +// 1/2 * sqrt(1/π) +#define SH_L0_NORMALIZATION 0.2820947917738781434740397257803862929220253146644994284220428608f +// 1/2 * sqrt(3/π) +#define SH_L1_NORMALIZATION 0.4886025119029199215863846228383470045758856081942277021382431574f +// sqrt(15/π)/2 +#define SH_L2_2_NORMALIZATION 1.0925484305920790705433857058026884026904329595042589753478516999f +// sqrt(15/π)/2 +#define SH_L2_1_NORMALIZATION SH_L2_2_NORMALIZATION +// sqrt(5/π)/4 +#define SH_L20_NORMALIZATION 0.3153915652525200060308936902957104933242475070484115878434078878f +// sqrt(15/π)/2 +#define SH_L21_NORMALIZATION SH_L2_2_NORMALIZATION +// sqrt(15/π)/4 +#define SH_L22_NORMALIZATION 0.5462742152960395352716928529013442013452164797521294876739258499f + +// Index into a buffer of floats representing contiguous L2 Spherical Harmonics coefficients, with separate channel and level. +uint SHIndex(uint probeIdx, uint channel, uint level) +{ + return probeIdx * SH_TOTAL_COEFFICIENTS + channel * SH_COEFFICIENTS_PER_CHANNEL + level; +} + +// Index into a buffer of floats representing contiguous L2 Spherical Harmonics coefficients, with the coefficient index. +uint SHIndex(uint probeIdx, uint coefficient) +{ + return probeIdx * SH_TOTAL_COEFFICIENTS + coefficient; +} + +// Basis function Y_0. +float SHL0() +{ + return SH_L0_NORMALIZATION; +} + +// Basis function Y_1,-1. +float SHL1_1(float3 direction) +{ + return SH_L1_NORMALIZATION * direction.x; +} + +// Basis function Y_1,0. +float SHL10(float3 direction) +{ + return SH_L1_NORMALIZATION * direction.y; +} + +// Basis function Y_1,1. +float SHL11(float3 direction) +{ + return SH_L1_NORMALIZATION * direction.z; +} + +// Basis function Y_2,-2. +float SHL2_2(float3 direction) +{ + return SH_L2_2_NORMALIZATION * direction.x * direction.y; +} + +// Basis function Y_2,-1. +float SHL2_1(float3 direction) +{ + return SH_L2_1_NORMALIZATION * direction.y * direction.z; +} + +// Basis function Y_2,0. +float SHL20(float3 direction) +{ + return SH_L20_NORMALIZATION * (3.0f * direction.z * direction.z - 1.0f); +} + +// Basis function Y_2,1. +float SHL21(float3 direction) +{ + return SH_L21_NORMALIZATION * direction.x * direction.z; +} + +// Basis function Y_2,2. +float SHL22(float3 direction) +{ + return SH_L22_NORMALIZATION * (direction.x * direction.x - direction.y * direction.y); +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl.meta new file mode 100644 index 00000000000..94c2dd35f3d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/SphericalHarmonicsUtils.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 53b08de14f6b2bb48be2613f00bbc94d +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl new file mode 100644 index 00000000000..618f29aa536 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl @@ -0,0 +1,50 @@ +#ifndef _PATHTRACING_STOCHASTICLIGHTMAPSAMPLING_HLSL_ +#define _PATHTRACING_STOCHASTICLIGHTMAPSAMPLING_HLSL_ + +#include "PathTracingCommon.hlsl" + +UnifiedRT::Ray MakeUVRay(float2 origin) +{ + UnifiedRT::Ray uvRay; + uvRay.direction = float3(0.0, 0.0, 1.0); + uvRay.tMax = 0.0002; + uvRay.tMin = 0.0; + uvRay.origin = float3(origin, -0.0001); + return uvRay; +}; + +UnifiedRT::Hit LightmapSampleTexelOffset( + float2 texelPos, + float2 texelOffset, + float2 resolution, + float2 instanceScale, + UnifiedRT::DispatchInfo dispatchInfo, + UnifiedRT::RayTracingAccelStruct uvAccelStruct, + half2 uvFallback) +{ + // No valid uv data for this texel? + if (uvFallback.x < 0) + return UnifiedRT::Hit::Invalid(); + + // select a random point in the texel + const float2 scaledResolution = resolution * instanceScale; + const float2 texelSample = (texelPos + texelOffset) / scaledResolution; + + // shoot a random ray towards the texel + const UnifiedRT::Ray uvRay = MakeUVRay(texelSample); + UnifiedRT::Hit randomUVHit = UnifiedRT::TraceRayClosestHit(dispatchInfo, uvAccelStruct, 0xFFFFFFFF, uvRay, 0); + if (randomUVHit.IsValid()) + { + return randomUVHit; + } + else + { + // use fallback uv + const float2 fallbackUvSample = (texelPos + uvFallback) / scaledResolution; + const UnifiedRT::Ray uvRay = MakeUVRay(fallbackUvSample); + return UnifiedRT::TraceRayClosestHit(dispatchInfo, uvAccelStruct, 0xFFFFFFFF, uvRay, 0); + } + return UnifiedRT::Hit::Invalid(); +} + +#endif diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl.meta new file mode 100644 index 00000000000..cffbcd470ad --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/StochasticLightmapSampling.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e713aa3169115ab43a1b2383e0e200e5 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl new file mode 100644 index 00000000000..87c75b64086 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl @@ -0,0 +1,82 @@ +#ifndef _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_ +#define _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_ + +// float3 transmission and Hit aliased to minimize payload size +struct PathTracingPayload +{ + UnifiedRT::Hit _hit; + + void Init(bool isShadowRay) + { + _hit = (UnifiedRT::Hit)0; + _hit.instanceID = -1; + _hit.isFrontFace = isShadowRay; + } + + bool IsShadowRay() + { + return _hit.isFrontFace; // before invoking ClosestHitExecute, isFrontFace holds isShadowRay + } + + UnifiedRT::Hit GetHit() + { + return _hit; + } + + void SetHit(UnifiedRT::Hit hit) + { + _hit = hit; + } + + void SetTransmission(float3 transmission) + { + _hit.uvBarycentrics = transmission.rg; + _hit.hitDistance = transmission.b; + } + + float3 GetTransmission() + { + return float3(_hit.uvBarycentrics, _hit.hitDistance); + } + + bool HasHit() + { + return _hit.IsValid(); + } + + void MarkHit() + { + _hit.instanceID = 1; + } +}; +#ifndef UNIFIED_RT_RAYGEN_FUNC +#define UNIFIED_RT_RAYGEN_FUNC RayGenExecute +#endif +#define UNIFIED_RT_ANYHIT_FUNC AnyHitExecute +#define UNIFIED_RT_CLOSESTHIT_FUNC ClosestHitExecute +#define UNIFIED_RT_PAYLOAD PathTracingPayload +#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/TraceRay.hlsl" + +UnifiedRT::Hit TraceRayClosestHit(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, uint instanceMask, UnifiedRT::Ray ray, uint rayFlags) +{ + PathTracingPayload payload; + payload.Init(false); + + UnifiedRT::TraceRay(dispatchInfo, accelStruct, instanceMask, ray, rayFlags | UnifiedRT::kRayFlagForceOpaque, payload); + + return payload.GetHit(); +} + +bool TraceShadowRay(UnifiedRT::DispatchInfo dispatchInfo, UnifiedRT::RayTracingAccelStruct accelStruct, uint instanceMask, UnifiedRT::Ray ray, uint rayFlags, out float3 transmission) +{ + PathTracingPayload payLoadShadow= (PathTracingPayload)0; + payLoadShadow.Init(true); + payLoadShadow.SetTransmission(1.0f); + + UnifiedRT::TraceRay(dispatchInfo, accelStruct, instanceMask, ray, rayFlags | UnifiedRT::kRayFlagAcceptFirstHitAndEndSearch, payLoadShadow); + + transmission = payLoadShadow.GetTransmission(); + return payLoadShadow.HasHit(); +} + +#endif // _UNIFIEDRAYTRACING_TRACERAYANDQUERYHIT_HLSL_ diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl.meta new file mode 100644 index 00000000000..0b42debcabf --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/TraceRayPathTracing.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3135a2ca4096d0d4197185f22349d08e +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs new file mode 100644 index 00000000000..ff9f222ee53 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs @@ -0,0 +1,135 @@ +using System; +using Unity.Mathematics; +using UnityEngine.Assertions; +using UnityEngine.Experimental.Rendering; + +namespace UnityEngine.PathTracing.Core +{ + static internal class SphericalHarmonicsUtil + { + // 1/2 * sqrt(1/π) + const float SH_L0_Normalization = 0.2820947917738781434740397257803862929220253146644994284220428608f; + // 1/2 * sqrt(3/π) + const float SH_L1_Normalization = 0.4886025119029199215863846228383470045758856081942277021382431574f; + // sqrt(15/π)/2 + const float SH_L2_2_Normalization = 1.0925484305920790705433857058026884026904329595042589753478516999f; + // sqrt(15/π)/2 + const float SH_L2_1_Normalization = SH_L2_2_Normalization; + // sqrt(5/π)/4 + const float SH_L20_Normalization = 0.3153915652525200060308936902957104933242475070484115878434078878f; + // sqrt(15/π)/2 + const float SH_L21_Normalization = SH_L2_2_Normalization; + // sqrt(15/π)/4 + const float SH_L22_Normalization = 0.5462742152960395352716928529013442013452164797521294876739258499f; + + + // Basis function Y_0. + static float SHL0() + { + return SH_L0_Normalization; + } + + // Basis function Y_1,-1. + static float SHL1_1(float3 direction) + { + return SH_L1_Normalization * direction.x; + } + + // Basis function Y_1,0. + static float SHL10(float3 direction) + { + return SH_L1_Normalization * direction.y; + } + + // Basis function Y_1,1. + static float SHL11(float3 direction) + { + return SH_L1_Normalization * direction.z; + } + + // Basis function Y_2,-2. + static float SHL2_2(float3 direction) + { + return SH_L2_2_Normalization * direction.x * direction.y; + } + + // Basis function Y_2,-1. + static float SHL2_1(float3 direction) + { + return SH_L2_1_Normalization * direction.y * direction.z; + } + + // Basis function Y_2,0. + static float SHL20(float3 direction) + { + return SH_L20_Normalization * (3.0f * direction.z * direction.z - 1.0f); + } + + // Basis function Y_2,1. + static float SHL21(float3 direction) + { + return SH_L21_Normalization * direction.x * direction.z; + } + + // Basis function Y_2,2. + static float SHL22(float3 direction) + { + return SH_L22_Normalization * (direction.x * direction.x - direction.y * direction.y); + } + + static public float3 EvaluateSH(Span sh, float3 direction) + { + float3 res = new float3(); + res += new float3(sh[0], sh[9] , sh[18]) * SHL0(); + res += new float3(sh[1], sh[10], sh[19]) * SHL1_1(direction); + res += new float3(sh[2], sh[11], sh[20]) * SHL10(direction); + res += new float3(sh[3], sh[12], sh[21]) * SHL11(direction); + res += new float3(sh[4], sh[13], sh[22]) * SHL2_2(direction); + res += new float3(sh[5], sh[14], sh[23]) * SHL2_1(direction); + res += new float3(sh[6], sh[15], sh[24]) * SHL20(direction); + res += new float3(sh[7], sh[16], sh[25]) * SHL21(direction); + res += new float3(sh[8], sh[17], sh[26]) * SHL22(direction); + return res; + } + + static public byte[] SHL2TolatLongEXR(float[] probesShData, int imageHeight, int maxProbeCount = 10) + { + Assert.AreEqual(probesShData.Length % 27, 0); + int probeCount = math.min(probesShData.Length / 27, maxProbeCount); + + int imageWidth = imageHeight * 2; + var outputColors = new Color[imageWidth * imageHeight * probeCount]; + + for (int probe = 0; probe < probeCount; ++probe) + { + for (int y = 0; y < imageHeight; y++) + { + for (int x = 0; x < imageWidth; x++) + { + float2 imageUv = (new float2(x, y) + 0.5f) / new float2(imageWidth, imageHeight); + float3 dir = LatlongCoordsToDirection(imageUv); + + var eval = SphericalHarmonicsUtil.EvaluateSH(new Span(probesShData, probe * 27, 27), dir); + outputColors[(y + probe * imageHeight) * imageWidth + x] = new Color(eval.x, eval.y, eval.z); + } + } + } + + return ImageConversion.EncodeArrayToEXR(outputColors, GraphicsFormat.R32G32B32A32_SFloat, (uint)imageWidth, (uint)(imageHeight * probeCount)); + } + + static float3 LatlongCoordsToDirection(float2 coord) + { + float theta = coord.y * math.PI; + float phi = (coord.x * 2.0f * math.PI); + + float cosTheta = math.cos(theta); + float sinTheta = math.sqrt(1.0f - math.min(1.0f, cosTheta * cosTheta)); + float cosPhi = math.cos(phi); + float sinPhi = math.sin(phi); + + float3 direction = new float3(sinTheta * cosPhi, cosTheta, sinTheta * sinPhi); + return direction; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs.meta new file mode 100644 index 00000000000..84cf9c3cb25 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/SphericalHarmonicsUtil.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ddb80635ace775b4cb05423d24e269fb \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs new file mode 100644 index 00000000000..67fd105fffd --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs @@ -0,0 +1,41 @@ +using System; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; +using BuildFlags = UnityEngine.Rendering.UnifiedRayTracing.BuildFlags; + +namespace UnityEngine.PathTracing.Integration +{ + internal class UVAccelerationStructure : IDisposable + { + internal IRayTracingAccelStruct _uvAS; + private GraphicsBuffer _buildScratchBuffer; + public void Dispose() + { + _uvAS?.Dispose(); + _uvAS = null; + _buildScratchBuffer?.Dispose(); + _buildScratchBuffer = null; + } + + public void Build(CommandBuffer commandBuffer, RayTracingContext rayTracingContext, UVMesh uvMesh, BuildFlags buildFlags) + { + var options = new AccelerationStructureOptions() { buildFlags = buildFlags }; + _uvAS = rayTracingContext.CreateAccelerationStructure(options); + Debug.Assert(_uvAS is not null); + + for (int i = 0; i < uvMesh.Mesh.subMeshCount; ++i) + { + var instanceDesc = new MeshInstanceDesc(uvMesh.Mesh, i) + { + mask = 0xFFFFFFFF, + instanceID = (uint)i + }; + _uvAS.AddInstance(instanceDesc); + } + + RayTracingHelper.ResizeScratchBufferForBuild(_uvAS, ref _buildScratchBuffer); + _uvAS.Build(commandBuffer, _buildScratchBuffer); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs.meta new file mode 100644 index 00000000000..88dfccd696e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVAccelerationStructure.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: abecb4219b2c230489d192eca9bd3469 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs new file mode 100644 index 00000000000..3d869a80cee --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs @@ -0,0 +1,196 @@ +using System; +using System.IO; +using Unity.Mathematics; +using UnityEngine.PathTracing.Core; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Integration +{ + internal static class UVFallbackBufferBuilderShaderIDs + { + public static readonly int VertexBuffer = Shader.PropertyToID("g_VertexBuffer"); + public static readonly int Width = Shader.PropertyToID("g_Width"); + public static readonly int Height = Shader.PropertyToID("g_Height"); + public static readonly int WidthScale = Shader.PropertyToID("g_WidthScale"); + public static readonly int HeightScale = Shader.PropertyToID("g_HeightScale"); + + public static readonly int UvFallback = Shader.PropertyToID("g_UvFallback"); + public static readonly int InstanceWidth = Shader.PropertyToID("g_InstanceWidth"); + public static readonly int InstanceHeight = Shader.PropertyToID("g_InstanceHeight"); + public static readonly int InstanceOffsetX = Shader.PropertyToID("g_InstanceOffsetX"); + public static readonly int InstanceOffsetY = Shader.PropertyToID("g_InstanceOffsetY"); + public static readonly int ChunkOffsetX = Shader.PropertyToID("g_ChunkOffsetX"); + public static readonly int ChunkOffsetY = Shader.PropertyToID("g_ChunkOffsetY"); + public static readonly int ChunkSize = Shader.PropertyToID("g_ChunkSize"); + public static readonly int InstanceWidthScale = Shader.PropertyToID("g_InstanceWidthScale"); + public static readonly int InstanceHeightScale = Shader.PropertyToID("g_InstanceHeightScale"); + } + + internal class UVFallbackBufferBuilder : IDisposable + { + private GraphicsBuffer _vertexBuffer; + private Material _uvFallbackBufferMaterial; + + public void Dispose() + { + _vertexBuffer?.Dispose(); + _vertexBuffer = null; + } + + public void Prepare(Material uvFallbackBufferMaterial) + { + _uvFallbackBufferMaterial = uvFallbackBufferMaterial; + } + + public void Build( + CommandBuffer cmd, + RenderTexture uvFallbackRT, + int width, + int height, + float widthScale, + float heightScale, + Mesh uvMesh) + { + cmd.BeginSample("Build UVFallbackBuffer"); + + Debug.Assert((UInt64)width * (UInt64)height < uint.MaxValue); + Debug.Assert(uvFallbackRT.format == RenderTextureFormat.RGFloat); + Debug.Assert(uvFallbackRT.depth > 0); + + // Clear the fallback buffer to -1, indicating no valid texels + cmd.SetRenderTarget(uvFallbackRT); + cmd.ClearRenderTarget(false, true, new Color(-1.0f, -1.0f, -1.0f, -1.0f)); + + // Expand vertices so we can do conservative rasterization + var originalVertices = uvMesh.vertices; + var originalIndices = uvMesh.triangles; + var vertices = new Vector2[originalIndices.Length]; + for (int i = 0; i < originalIndices.Length; i++) + { + vertices[i] = originalVertices[originalIndices[i]]; + } + + // Reallocate the vertex buffer if necessary, write the vertices + if (_vertexBuffer == null || _vertexBuffer.count < vertices.Length) + { + _vertexBuffer?.Dispose(); + _vertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, vertices.Length, 2 * sizeof(float)); + } + cmd.SetBufferData(_vertexBuffer, vertices); + + // Build the fallback buffer using conservative rasterization + cmd.SetGlobalBuffer(UVFallbackBufferBuilderShaderIDs.VertexBuffer, _vertexBuffer); + cmd.SetGlobalInteger(UVFallbackBufferBuilderShaderIDs.Width, width); + cmd.SetGlobalInteger(UVFallbackBufferBuilderShaderIDs.Height, height); + cmd.SetGlobalFloat(UVFallbackBufferBuilderShaderIDs.WidthScale, widthScale); + cmd.SetGlobalFloat(UVFallbackBufferBuilderShaderIDs.HeightScale, heightScale); + cmd.DrawProcedural(Matrix4x4.identity, _uvFallbackBufferMaterial, 0, MeshTopology.Triangles, (int)uvMesh.GetTotalIndexCount()); + + cmd.EndSample("Build UVFallbackBuffer"); + + GraphicsHelpers.Flush(cmd); + } + } + + internal class UVFallbackBuffer : IDisposable + { + public RenderTexture UVFallbackRT; + public float WidthScale; + public float HeightScale; + public int Width => UVFallbackRT.width; + public int Height => UVFallbackRT.height; + + public void Dispose() + { + UVFallbackRT?.Release(); + if (UVFallbackRT != null) + CoreUtils.Destroy(UVFallbackRT); + UVFallbackRT = null; + } + + public bool Build( + CommandBuffer commandBuffer, + UVFallbackBufferBuilder builder, + int width, + int height, + UVMesh uvMesh) + { + if (width == 0 || height == 0) + return false; + // Assume that the largest edge of UV bounds can fit into the texture, + // and calculate the scale factor for each dimension to achieve this. + // This is accounting for the aspect ratio of the UV bounds. + var uvAspectRatio = uvMesh.UVAspectRatio; + if (uvAspectRatio >= 1.0f) // width >= height + { + WidthScale = 1.0f; + HeightScale = (width / uvAspectRatio) / height; + } + else // width < height + { + WidthScale = (height * uvAspectRatio) / width; + HeightScale = 1.0f; + } + + // If we find that the scaled UV bounds still don't fit into the texture, + // then we uniformly scale down the bounds to fit. + // This is accounting for the aspect ratio of the texture. + if (HeightScale > 1) + { + WidthScale /= HeightScale; + HeightScale = 1.0f; + } + if (WidthScale > 1) + { + HeightScale /= WidthScale; + WidthScale = 1.0f; + } + + UVFallbackRT = new RenderTexture(width, height, 24, RenderTextureFormat.RGFloat, RenderTextureReadWrite.Linear) + { + name = "UVFallbackRT", + hideFlags = HideFlags.HideAndDontSave, + enableRandomWrite = true, + useMipMap = false, + autoGenerateMips = false, + }; + UVFallbackRT.Create(); + + builder.Build(commandBuffer, UVFallbackRT, width, height, WidthScale, HeightScale, uvMesh.Mesh); + return true; + } + + public void Bind(CommandBuffer cmd, IRayTracingShader shader, Vector2Int instanceOffset) + { + shader.SetTextureParam(cmd, UVFallbackBufferBuilderShaderIDs.UvFallback, UVFallbackRT); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceWidth, UVFallbackRT.width); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceHeight, UVFallbackRT.height); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceOffsetX, instanceOffset.x); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceOffsetY, instanceOffset.y); + shader.SetFloatParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceWidthScale, WidthScale); + shader.SetFloatParam(cmd, UVFallbackBufferBuilderShaderIDs.InstanceHeightScale, HeightScale); + } + + public void BindChunked(CommandBuffer cmd, IRayTracingShader shader, Vector2Int instanceOffset, uint2 chunkOffset, uint chunkSize) + { + Bind(cmd, shader, instanceOffset); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.ChunkOffsetX, (int)chunkOffset.x); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.ChunkOffsetY, (int)chunkOffset.y); + shader.SetIntParam(cmd, UVFallbackBufferBuilderShaderIDs.ChunkSize, (int)chunkSize); + } + + public void Bind(CommandBuffer cmd, ComputeShader shader, int kernelIndex, Vector2Int instanceOffset) + { + cmd.SetComputeTextureParam(shader, kernelIndex, UVFallbackBufferBuilderShaderIDs.UvFallback, UVFallbackRT); + cmd.SetComputeIntParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceWidth, UVFallbackRT.width); + cmd.SetComputeIntParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceHeight, UVFallbackRT.height); + cmd.SetComputeIntParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceOffsetX, instanceOffset.x); + cmd.SetComputeIntParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceOffsetY, instanceOffset.y); + cmd.SetComputeFloatParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceWidthScale, WidthScale); + cmd.SetComputeFloatParam(shader, UVFallbackBufferBuilderShaderIDs.InstanceHeightScale, HeightScale); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs.meta new file mode 100644 index 00000000000..149c53e2763 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVFallbackBuffer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aba42b4aba376bf4da5d008c3a7ed47a \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs new file mode 100644 index 00000000000..5823c54409e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs @@ -0,0 +1,135 @@ +using System; +using Unity.Collections; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Integration +{ + internal class UVMesh : IDisposable + { + public Mesh Mesh; + public float UVAspectRatio; // width / height + + public void Dispose() + { + Object.DestroyImmediate(Mesh); + } + + private struct OutputVertex + { + public Vector3 Position; + }; + + public bool Build(Mesh mesh) + { +#if UNITY_EDITOR + var inputDataArray = UnityEditor.MeshUtility.AcquireReadOnlyMeshData(mesh); +#else + var inputDataArray = Mesh.AcquireReadOnlyMeshData(mesh); +#endif + var outputDataArray = Mesh.AllocateWritableMeshData(inputDataArray.Length); + if (inputDataArray.Length != 1) + { + Debug.Assert(inputDataArray.Length == 1); + return false; + } + int vertexCount = inputDataArray[0].vertexCount; + var tmpVtxArray0 = new NativeArray(vertexCount, Allocator.TempJob); + var ok = Build(outputDataArray[0], inputDataArray[0], tmpVtxArray0, out UVAspectRatio); + tmpVtxArray0.Dispose(); + inputDataArray.Dispose(); + if (!ok) + return false; + Mesh = new Mesh + { + hideFlags = HideFlags.DontSaveInEditor, + name = mesh.name + }; + Mesh.ApplyAndDisposeWritableMeshData(outputDataArray, Mesh); + Mesh.RecalculateBounds(); + Mesh.UploadMeshData(false); // should be passing true, but this doesn't work in playmode + return true; + } + + // Make a new mesh which position values are taken from the UVs of the input mesh + private static bool Build(Mesh.MeshData outputMesh, Mesh.MeshData inputMesh, NativeArray tmpVtxArray0, out float uvAspectRatio) + { + uvAspectRatio = 0f; + + // check that the input mesh has uv2 or uv + if (!(inputMesh.HasVertexAttribute(VertexAttribute.TexCoord0) || inputMesh.HasVertexAttribute(VertexAttribute.TexCoord1))) + return false; + + // work out normalized uv bounds + int vertexCount = inputMesh.vertexCount; + var inputUVData = tmpVtxArray0; + inputMesh.GetUVs(inputMesh.HasVertexAttribute(VertexAttribute.TexCoord1) ? 1 : 0, inputUVData); + + LightmapIntegrationHelpers.ComputeUVBounds(inputUVData, out Vector2 uvBoundSize, out Vector2 uvBoundsOffset); + if (!(uvBoundSize.x > 0.0f && uvBoundSize.y > 0.0f)) + return false; + + Vector2 normalizationOffset = -uvBoundsOffset; + Vector2 normalizationScale = new Vector2(1.0f / uvBoundSize.x, 1.0f / uvBoundSize.y); + + uvAspectRatio = uvBoundSize.x / uvBoundSize.y; + + var layout = new[] + { + new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), + }; + outputMesh.SetVertexBufferParams(vertexCount, layout); + var outputVertexData = outputMesh.GetVertexData(); + for (int i = 0; i < vertexCount; i++) + { + OutputVertex outputVertex = new OutputVertex + { + Position = new Vector3((inputUVData[i].x + normalizationOffset.x) * normalizationScale.x, (inputUVData[i].y + normalizationOffset.y) * normalizationScale.y , 0.0f), + }; + outputVertexData[i] = outputVertex; + } + + // Copy the index buffer across + var subMeshCount = inputMesh.subMeshCount; + outputMesh.subMeshCount = subMeshCount; + int indexCount; + if (inputMesh.indexFormat == IndexFormat.UInt16) + indexCount = inputMesh.GetIndexData().Length; + else if (inputMesh.indexFormat == IndexFormat.UInt32) + indexCount = inputMesh.GetIndexData().Length; + else + return false; + outputMesh.SetIndexBufferParams(indexCount, inputMesh.indexFormat); + + if (inputMesh.indexFormat == IndexFormat.UInt16) + { + var inputIndices = inputMesh.GetIndexData(); + var outputIndices = outputMesh.GetIndexData(); + for (int i = 0; i < inputIndices.Length; i++) + { + outputIndices[i] = inputIndices[i]; + } + } + else if (inputMesh.indexFormat == IndexFormat.UInt32) + { + var inputIndices = inputMesh.GetIndexData(); + var outputIndices = outputMesh.GetIndexData(); + for (int i = 0; i < inputIndices.Length; i++) + { + outputIndices[i] = inputIndices[i]; + } + } + else + { + return false; + } + + for (int sm = 0; sm < subMeshCount; ++sm) + { + SubMeshDescriptor smd = inputMesh.GetSubMesh(sm); + outputMesh.SetSubMesh(sm, smd); + } + return true; + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs.meta new file mode 100644 index 00000000000..0309f2e3b74 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UVMesh.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1968508719bcdd84cacddbaa045ff743 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef new file mode 100644 index 00000000000..4498809be81 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef @@ -0,0 +1,31 @@ +{ + "name": "Unity.PathTracing.Runtime", + "rootNamespace": "", + "references": [ + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:df380645f10b7bc4b97d4f5eb6303d95", + "GUID:e3bf50bf561c9a7418912816ec4cb8e1", + "GUID:214c0945bb158c940aada223f3223ee8", + "GUID:d8b63aba1907145bea998dd612889d6b" + ], + "includePlatforms": [ + "Editor", + "LinuxStandalone64", + "macOSStandalone", + "WindowsStandalone64" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.rendering.denoising", + "expression": "2.0.0", + "define": "ENABLE_UNITY_DENOISING_PLUGIN" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef.meta new file mode 100644 index 00000000000..83a3221a5b3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Unity.PathTracing.Runtime.asmdef.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: c49c619b6af2be941af9bcbca2641964 +AssemblyDefinitionImporter: + externalObjects: {} + userData: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs new file mode 100644 index 00000000000..78267abb1e8 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs @@ -0,0 +1,205 @@ +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.LightTransport; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Core +{ + internal class UnityComputeDeviceContext : IDeviceContext + { + private readonly Dictionary _buffers = new(); + private readonly HashSet _inProgressRequests = new(); + private readonly HashSet _failedRequests = new(); + private readonly HashSet _successfulRequests = new(); + private uint _nextFreeBufferId; + private uint _nextFreeEventId; + private CommandBuffer _cmdBuffer; + + private List _temporaryBuffers = new(); + + private void CreateCommandBuffer() + { + _cmdBuffer?.Dispose(); + _cmdBuffer = new CommandBuffer(); + _cmdBuffer.name = "UnityComputeDeviceContextCommandBuffer"; + } + + public BufferID CreateBuffer(ulong count, ulong stride) + { + Debug.Assert(count != 0, "Buffer element count cannot be zero."); + Debug.Assert(stride != 0, "Stride cannot be zero."); + Debug.Assert(stride % 4 == 0, "Stride must be a multiple of 4."); + Debug.Assert(stride <= 2048, "Stride must be 2048 or less."); + GraphicsBuffer buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)count, (int)stride); + Debug.Assert(buffer.IsValid(), "Buffer was not successfully created."); + var zeros = new NativeArray((int)(count * stride), Allocator.Temp, NativeArrayOptions.ClearMemory); + buffer.SetData(zeros); + zeros.Dispose(); + + var idInteger = _nextFreeBufferId++; + var id = new BufferID(idInteger); + _buffers[id] = buffer; + return id; + } + + public void DestroyBuffer(BufferID id) + { + Debug.Assert(_buffers.ContainsKey(id), "Invalid buffer ID given."); + + _buffers[id].Release(); + _buffers.Remove(id); + } + + public void Dispose() + { + ReleaseTemporaryBuffers(); + _cmdBuffer?.Dispose(); + } + + public bool Flush() + { + Debug.Assert(_cmdBuffer != null); + Graphics.ExecuteCommandBuffer(_cmdBuffer); + + // TODO(pema.malling): Don't block here https://jira.unity3d.com/browse/LIGHT-1699 + // Ideally we shouldn't need this, but if we don't do it, read-backs will never finish unless explicitly waited on. + AsyncGPUReadback.WaitAllRequests(); + + ReleaseTemporaryBuffers(); + + CreateCommandBuffer(); + return true; + } + + public bool Initialize() + { + CreateCommandBuffer(); + return true; + } + + public bool IsCompleted(EventID id) + { + return _successfulRequests.Contains(id) || _failedRequests.Contains(id); + } + + public bool Wait(EventID id) + { + AsyncGPUReadback.WaitAllRequests(); + + if (_failedRequests.Contains(id)) + { + return false; + } + return true; + } + + public void ReadBuffer(BufferSlice src, NativeArray result) where T : struct + { + Debug.Assert(_buffers.ContainsKey(src.Id), "Invalid buffer ID given."); + + int stride = UnsafeUtility.SizeOf(); + int offset = (int)src.Offset * stride; + int size = result.Length * stride; + _cmdBuffer.RequestAsyncReadbackIntoNativeArray(ref result, _buffers[src.Id], size, offset, delegate { }); + } + + public void ReadBuffer(BufferSlice src, NativeArray result, EventID id) where T : struct + { + Debug.Assert(_buffers.ContainsKey(src.Id), "Invalid buffer ID given."); + + int stride = UnsafeUtility.SizeOf(); + int offset = (int)src.Offset * stride; + int size = result.Length * stride; + _cmdBuffer.RequestAsyncReadbackIntoNativeArray(ref result, _buffers[src.Id], size, offset, request => + { + Debug.Assert(request.done); + // The user may have destroyed the event before the readback was completed, so we check if its still there. + if (_inProgressRequests.Remove(id)) + { + if (request.hasError) + { + _failedRequests.Add(id); + } + else + { + _successfulRequests.Add(id); + } + } + }); + _inProgressRequests.Add(id); + } + + public void WriteBuffer(BufferSlice dst, NativeArray src) + where T : struct + { + Debug.Assert(_buffers.ContainsKey(dst.Id), "Invalid buffer ID given."); + + _cmdBuffer.SetBufferData(_buffers[dst.Id], src, 0, (int)dst.Offset, src.Length); + } + + public void WriteBuffer(BufferSlice dst, NativeArray src, EventID id) + where T : struct + { + Debug.Assert(_buffers.ContainsKey(dst.Id), "Invalid buffer ID given."); + + _cmdBuffer.SetBufferData(_buffers[dst.Id], src, 0, (int)dst.Offset, src.Length); + + _successfulRequests.Add(id); + } + + public EventID CreateEvent() + { + var eventIdInteger = _nextFreeEventId++; + var eventId = new EventID(eventIdInteger); + return eventId; + } + + public void DestroyEvent(EventID id) + { + if (_inProgressRequests.Contains(id)) + { + _inProgressRequests.Remove(id); + } + if (_failedRequests.Contains(id)) + { + _failedRequests.Remove(id); + } + if (_successfulRequests.Contains(id)) + { + _successfulRequests.Remove(id); + } + } + + public GraphicsBuffer GetComputeBuffer(BufferID id) + { + Debug.Assert(_buffers.ContainsKey(id), "Invalid buffer ID given."); + return _buffers[id]; + } + + public CommandBuffer GetCommandBuffer() + { + return _cmdBuffer; + } + + // Temporary buffers are valid until the next call to Flush(). + public BufferID GetTemporaryBuffer(ulong count, ulong stride) + { + BufferID bufferID = CreateBuffer(count, stride); + _temporaryBuffers.Add(bufferID); + return bufferID; + } + + private void ReleaseTemporaryBuffers() + { + foreach (var bufferId in _temporaryBuffers) + { + if (_buffers.ContainsKey(bufferId)) + { + DestroyBuffer(bufferId); + } + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs.meta new file mode 100644 index 00000000000..f3f82a35ff7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeDeviceContext.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8dc56c58c141cc1409d255b134a28057 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs new file mode 100644 index 00000000000..a8c64fd083c --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs @@ -0,0 +1,214 @@ +using System; +using System.IO; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEditor; +using UnityEngine.LightTransport; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Integration +{ + internal class UnityComputeProbeIntegrator : IProbeIntegrator + { + private readonly ProbeIntegrator _probeIntegrator; + private UnityComputeWorld _world; + private uint _bounceCount; + private uint _directLightingEvaluationCount; + private uint _numIndirectLightingEvaluations; + private uint _basePositionsOffset; + + private static class ShaderProperties + { + public static readonly int MappingTable = Shader.PropertyToID("g_MappingTable"); + public static readonly int PerProbeLightIndicesInput = Shader.PropertyToID("g_PerProbeLightIndicesInput"); + public static readonly int PerProbeLightIndicesOutput = Shader.PropertyToID("g_PerProbeLightIndicesOutput"); + public static readonly int PerProbeLightIndicesInputOffset = Shader.PropertyToID("g_PerProbeLightIndicesInputOffset"); + public static readonly int MaxLightsPerProbe = Shader.PropertyToID("g_MaxLightsPerProbe"); + public static readonly int ProbeCount = Shader.PropertyToID("g_ProbeCount"); + } + private ComputeShader _probeOcclusionLightIndexMappingShader; + private int _probeOcclusionLightIndexMappingKernel; + private Rendering.Sampling.SamplingResources _samplingResources; + private ProbeIntegratorResources _integrationResources; + + public UnityComputeProbeIntegrator(bool countNEERayAsPathSegment, Rendering.Sampling.SamplingResources samplingResources, ProbeIntegratorResources integrationResources, ComputeShader probeOcclusionLightIndexMappingShader) + { + _probeIntegrator = new ProbeIntegrator(countNEERayAsPathSegment); + _samplingResources = samplingResources; + _probeOcclusionLightIndexMappingShader = probeOcclusionLightIndexMappingShader; + _probeOcclusionLightIndexMappingKernel = _probeOcclusionLightIndexMappingShader.FindKernel("MapIndices"); + _integrationResources = integrationResources; + } + + public void Dispose() + { + _probeIntegrator.Dispose(); + } + + public IProbeIntegrator.Result IntegrateDirectRadiance(IDeviceContext context, int positionOffset, int positionCount, int sampleCount, + bool ignoreEnvironment, BufferSlice radianceEstimateOut) + { + UnityComputeDeviceContext unifiedContext = context as UnityComputeDeviceContext; + Debug.Assert(unifiedContext != null); + Debug.Assert(sampleCount > 0); + + ProbeIntegrator.GetRadianceScratchBufferSizesInDwords((uint)positionCount, (uint)sampleCount, out uint expansionBufferSize, out uint reductionBufferSize); + var expansionBuffer = unifiedContext.GetTemporaryBuffer(expansionBufferSize, sizeof(float)); + var reductionBuffer = unifiedContext.GetTemporaryBuffer(reductionBufferSize, sizeof(float)); + + uint sampleOffset = 0; + _probeIntegrator.EstimateDirectRadianceShl2( + unifiedContext.GetCommandBuffer(), + _world.PathTracingWorld, + _basePositionsOffset + (uint)positionOffset, + (uint)positionCount, + sampleOffset, + (uint)sampleCount, + _directLightingEvaluationCount, + ignoreEnvironment, + unifiedContext.GetComputeBuffer(radianceEstimateOut.Id), + (uint)radianceEstimateOut.Offset, + unifiedContext.GetComputeBuffer(expansionBuffer), + unifiedContext.GetComputeBuffer(reductionBuffer)); + + return new IProbeIntegrator.Result(IProbeIntegrator.ResultType.Success, string.Empty); + } + + public IProbeIntegrator.Result IntegrateIndirectRadiance(IDeviceContext context, int positionOffset, int positionCount, int sampleCount, + bool ignoreEnvironment, BufferSlice radianceEstimateOut) + { + var unifiedContext = context as UnityComputeDeviceContext; + Debug.Assert(unifiedContext != null); + Debug.Assert(sampleCount > 0); + + ProbeIntegrator.GetRadianceScratchBufferSizesInDwords((uint)positionCount, (uint)sampleCount, out uint expansionBufferSize, out uint reductionBufferSize); + var expansionBuffer = unifiedContext.GetTemporaryBuffer(expansionBufferSize, sizeof(float)); + var reductionBuffer = unifiedContext.GetTemporaryBuffer(reductionBufferSize, sizeof(float)); + + uint sampleOffset = 0; + _probeIntegrator.EstimateIndirectRadianceShl2( + unifiedContext.GetCommandBuffer(), + _world.PathTracingWorld, + _basePositionsOffset + (uint)positionOffset, + (uint)positionCount, + _bounceCount, + sampleOffset, + (uint)sampleCount, + _numIndirectLightingEvaluations, + ignoreEnvironment, + unifiedContext.GetComputeBuffer(radianceEstimateOut.Id), + (uint)radianceEstimateOut.Offset, + unifiedContext.GetComputeBuffer(expansionBuffer), + unifiedContext.GetComputeBuffer(reductionBuffer)); + + return new IProbeIntegrator.Result(IProbeIntegrator.ResultType.Success, string.Empty); + } + + public IProbeIntegrator.Result IntegrateValidity(IDeviceContext context, int positionOffset, int positionCount, int sampleCount, BufferSlice validityEstimateOut) + { + UnityComputeDeviceContext unifiedContext = context as UnityComputeDeviceContext; + Debug.Assert(unifiedContext != null); + Debug.Assert(sampleCount > 0); + + ProbeIntegrator.GetValidityScratchBufferSizesInDwords((uint)positionCount, (uint)sampleCount, out uint expansionBufferSize, out uint reductionBufferSize); + var expansionBuffer = unifiedContext.GetTemporaryBuffer(expansionBufferSize, sizeof(float)); + var reductionBuffer = unifiedContext.GetTemporaryBuffer(reductionBufferSize, sizeof(float)); + + uint sampleOffset = 0; + _probeIntegrator.EstimateValidity( + unifiedContext.GetCommandBuffer(), + _world.PathTracingWorld, + _basePositionsOffset + (uint)positionOffset, + (uint)positionCount, + sampleOffset, + (uint)sampleCount, + unifiedContext.GetComputeBuffer(validityEstimateOut.Id), + (uint)validityEstimateOut.Offset, + unifiedContext.GetComputeBuffer(expansionBuffer), + unifiedContext.GetComputeBuffer(reductionBuffer)); + + return new IProbeIntegrator.Result(IProbeIntegrator.ResultType.Success, string.Empty); + } + + public IProbeIntegrator.Result IntegrateOcclusion(IDeviceContext context, int positionOffset, int positionCount, int sampleCount, + int maxLightsPerProbe, BufferSlice perProbeLightIndices, BufferSlice probeOcclusionEstimateOut) + { + UnityComputeDeviceContext unifiedContext = context as UnityComputeDeviceContext; + Debug.Assert(unifiedContext != null); + Debug.Assert(maxLightsPerProbe > 0); + Debug.Assert(sampleCount > 0); + + var cmd = unifiedContext.GetCommandBuffer(); + + // The input per-probe light indices refer to elements of the light list used by LightBaker (ie. BakeInput.lightData). + // This is by necessity, since that is the contract of the IProbeIntegrator interface, and the ordering of lights may be considered 'global'. + // However, ProbeIntegrator needs per-probe light indices that refer to elements of the light list used by the path tracer + // (ie. World.LightList) in order to access the light data in shader. We therefore need to convert the input indices. + int[] lightIndexMapping = new int[_world.LightHandles.Length]; + for (int lightIndex = 0; lightIndex < lightIndexMapping.Length; lightIndex++) + { + var lightHandle = _world.LightHandles[lightIndex]; + int worldLightIndex = _world.PathTracingWorld.LightHandleToLightListIndex[lightHandle]; + lightIndexMapping[lightIndex] = worldLightIndex; + } + if (lightIndexMapping.Length == 0) // Avoid 0-sized buffer in case of no lights in scene + lightIndexMapping = new int[] { -1 }; + using NativeArray lightIndexMappingArray = new(lightIndexMapping, Allocator.Temp); + var lightIndexMappingBuffer = unifiedContext.GetTemporaryBuffer((ulong)lightIndexMapping.Length, sizeof(int)); + unifiedContext.WriteBuffer(lightIndexMappingBuffer.Slice(), lightIndexMappingArray); + var perProbeLightIndicesWorld = unifiedContext.GetTemporaryBuffer((ulong)(positionCount * maxLightsPerProbe), sizeof(int)); + cmd.SetComputeBufferParam(_probeOcclusionLightIndexMappingShader, _probeOcclusionLightIndexMappingKernel, ShaderProperties.MappingTable, unifiedContext.GetComputeBuffer(lightIndexMappingBuffer)); + cmd.SetComputeBufferParam(_probeOcclusionLightIndexMappingShader, _probeOcclusionLightIndexMappingKernel, ShaderProperties.PerProbeLightIndicesInput, unifiedContext.GetComputeBuffer(perProbeLightIndices.Id)); + cmd.SetComputeBufferParam(_probeOcclusionLightIndexMappingShader, _probeOcclusionLightIndexMappingKernel, ShaderProperties.PerProbeLightIndicesOutput, unifiedContext.GetComputeBuffer(perProbeLightIndicesWorld)); + cmd.SetComputeIntParam(_probeOcclusionLightIndexMappingShader, ShaderProperties.PerProbeLightIndicesInputOffset, (int)perProbeLightIndices.Offset); + cmd.SetComputeIntParam(_probeOcclusionLightIndexMappingShader, ShaderProperties.MaxLightsPerProbe, maxLightsPerProbe); + cmd.SetComputeIntParam(_probeOcclusionLightIndexMappingShader, ShaderProperties.ProbeCount, positionCount); + _probeOcclusionLightIndexMappingShader.GetKernelThreadGroupSizes(_probeOcclusionLightIndexMappingKernel, out uint threadGroupSizeX, out _, out _); + cmd.DispatchCompute(_probeOcclusionLightIndexMappingShader, _probeOcclusionLightIndexMappingKernel, GraphicsHelpers.DivUp(positionCount, threadGroupSizeX), 1, 1); + + ProbeIntegrator.GetOcclusionScratchBufferSizesInDwords((uint)maxLightsPerProbe, (uint)positionCount, (uint)sampleCount, out uint expansionBufferSize, out uint reductionBufferSize); + var expansionBuffer = unifiedContext.GetTemporaryBuffer(expansionBufferSize, sizeof(float)); + var reductionBuffer = unifiedContext.GetTemporaryBuffer(reductionBufferSize, sizeof(float)); + + uint sampleOffset = 0; + _probeIntegrator.EstimateLightOcclusion( + cmd, + _world.PathTracingWorld, + _basePositionsOffset + (uint)positionOffset, + (uint)positionCount, + sampleOffset, + (uint)sampleCount, + (uint)maxLightsPerProbe, + unifiedContext.GetComputeBuffer(perProbeLightIndicesWorld), + 0u, + unifiedContext.GetComputeBuffer(probeOcclusionEstimateOut.Id), + (uint)probeOcclusionEstimateOut.Offset, + unifiedContext.GetComputeBuffer(expansionBuffer), + unifiedContext.GetComputeBuffer(reductionBuffer)); + + return new IProbeIntegrator.Result(IProbeIntegrator.ResultType.Success, string.Empty); + } + + public void Prepare(IDeviceContext context, IWorld world, BufferSlice positions, float pushoff, int bounceCount) + { + _bounceCount = (uint)bounceCount; + _directLightingEvaluationCount = 4; + _numIndirectLightingEvaluations = 1; + + _world = world as UnityComputeWorld; + Debug.Assert(world != null); + + UnityComputeDeviceContext unifiedContext = context as UnityComputeDeviceContext; + Debug.Assert(unifiedContext != null); + + _basePositionsOffset = (uint)positions.Offset; + Debug.Assert(_world != null, nameof(_world) + " != null"); + + _probeIntegrator.Prepare(unifiedContext.GetComputeBuffer(positions.Id), _integrationResources, _samplingResources); + } + + public void SetProgressReporter(BakeProgressState progressState) => _probeIntegrator.SetProgressReporter(progressState); + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs.meta new file mode 100644 index 00000000000..109ddc687c3 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeProbeIntegrator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 481c6ce75c5ff4045a0f508e2a71ac21 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs new file mode 100644 index 00000000000..c79af2acdd5 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using UnityEngine.PathTracing.Core; +using UnityEngine.LightTransport; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Integration +{ + using LightHandle = Handle; + + internal class UnityComputeWorld : IWorld + { + internal World PathTracingWorld; + internal GraphicsBuffer ScratchBuffer; + internal Mesh[] Meshes; + internal LightHandle[] LightHandles; + internal RayTracingContext RayTracingContext; + internal readonly List TemporaryObjects = new(); + + internal const uint RenderingObjectLayer = 1 << 0; + + internal void BuildAccelerationStructure(CommandBuffer cmd) + { + PathTracingWorld.GetAccelerationStructure().Build(cmd, ref ScratchBuffer); + } + + public void Init(RayTracingContext rayTracingContext, WorldResourceSet worldResources) + { + PathTracingWorld = new World(); + PathTracingWorld.Init(rayTracingContext, worldResources); + RayTracingContext = rayTracingContext; + } + + public void Dispose() + { + PathTracingWorld?.Dispose(); + ScratchBuffer?.Dispose(); + + foreach (var obj in TemporaryObjects) + CoreUtils.Destroy(obj); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs.meta new file mode 100644 index 00000000000..3c6f3c277fa --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/UnityComputeWorld.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e15d7f7899feb9845b57a4c08e56d1c8 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs new file mode 100644 index 00000000000..7eb053ab624 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs @@ -0,0 +1,965 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using System.Runtime.InteropServices; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Core +{ + using InstanceHandle = Handle; + using InstanceHandleSet = HandleSet; + + using LightHandle = Handle; + using LightHandleSet = HandleSet; + + using MaterialHandle = Handle; + using MaterialHandleSet = HandleSet; + + internal enum RenderedGameObjectsFilter + { + All = 0, + OnlyStatic = 1, + AllInCameraRaysThenOnlyStatic = 2 + } + internal enum LightPickingMethod + { + Uniform = 0, + Regir, + LightGrid + } + internal enum InstanceFlags + { + DIRECT_RAY_VIS_MASK = 1, + INDIRECT_RAY_VIS_MASK = 2, + SHADOW_RAY_VIS_MASK = 4, + CURRENT_LOD_FOR_LIGHTMAP_INSTANCE = 8, + LOD_ZERO_FOR_LIGHTMAP_INSTANCE = 16, + CURRENT_LOD_FOR_LIGHTMAP_INSTANCE_SHADOW = 32, + LOD_ZERO_FOR_LIGHTMAP_INSTANCE_SHADOW = 64 + } + + internal class World : IDisposable + { + internal const int EMISSIVE_MESH = 8; // Must match the EMISSIVE_MESH define in Common.hlsl + internal const int ENVIRONMENT_LIGHT = 9; // Must match the ENVIRONMENT_LIGHT define in Common.hlsl + + // This trivial type only exists so that handles can be type-safe. + // If we make an InstanceDescriptor type, we can use that instead. + internal readonly struct InstanceKey { } + + internal struct MaterialDescriptor + { + public Texture Albedo; + public Vector2 AlbedoScale; + public Vector2 AlbedoOffset; + + public Texture Emission; + public Vector2 EmissionScale; + public Vector2 EmissionOffset; + public Vector3 EmissionColor; + public MaterialPropertyType EmissionType; + + public Texture Transmission; + public Vector2 TransmissionScale; + public Vector2 TransmissionOffset; + public TransmissionChannels TransmissionChannels; + + public float Alpha; + public float AlphaCutoff; + public bool UseAlphaCutoff; + + public bool DoubleSidedGI; + + public bool PointSampleTransmission; + } + + internal struct LightDescriptor + { + public LightType Type; + public Vector3 LinearLightColor; + public LightShadows Shadows; + public Matrix4x4 Transform; + public float ColorTemperature; + public LightmapBakeType LightmapBakeType; + public Experimental.GlobalIllumination.FalloffType FalloffType; + public Vector2 AreaSize; + public float SpotAngle; + public float InnerSpotAngle; + public uint CullingMask; + public float BounceIntensity; + public float Range; + public int ShadowMaskChannel; + public bool UseColorTemperature; + public float ShadowRadius; + public Texture CookieTexture; + public float CookieSize; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct PTLight // Must match the PTLight definition in Common.hlsl + { + public Vector3 position; + public int type; + public Vector3 intensity; + public int castShadows; + public Vector3 forward; + public int contributesToDirectLighting; + public Vector4 attenuation; + public Vector3 up; + public float width; + public Vector3 right; + public float height; + public uint layerMask; + public float indirectScale; + public float spotAngle; + public float innerSpotAngle; + public float range; + public int shadowMaskChannel; + public int falloffIndex; + public float shadowRadius; + public int cookieIndex; + + public override readonly int GetHashCode() + { + return HashCode.Combine(position, type, intensity, castShadows, forward, contributesToDirectLighting, range) + ^ HashCode.Combine(attenuation, up, width, right, height, layerMask, indirectScale, falloffIndex); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ThinReservoir + { + public int LightIndex; + public float Weight; + } + + private readonly InstanceHandleSet _instanceHandleSet = new(); + private readonly MaterialHandleSet _materialHandleSet = new(); + + #region Lighting State + internal class LightState + { + public LightPickingMethod lightPickingMethod; + + // Holds a list of light sources in the scene + public readonly List LightList = new(64); + public ComputeBuffer LightListBuffer; + + // Holds a list of light falloff LUTs + public const uint LightFalloffLUTLength = 1024; + public List LightFalloffDescs = new(); + public float[] LightFalloff = null; // The light falloff LUT tables + public float[] LightFalloffLUTRanges = null; // The range that each LUT applies to + public ComputeBuffer LightFalloffBuffer; + public ComputeBuffer LightFalloffLUTRangeBuffer; + + // Light dictionary (Handle, Light entry) + public Dictionary LightHandleToLightListEntry = new(); + + // Lookup table from LightHandle to the corresponding index in LightList. Only used for baking. + public Dictionary LightHandleToLightListIndex = new(); + + // Mesh light dictionary (SubMeshHash, emissive mesh entry) + public Dictionary MeshLights = new(); + + // Light handle set + public LightHandleSet LightHandleSet = new(); + + public bool HasEnvironmentLight = false; + + public int EnvLightCount => HasEnvironmentLight ? 1 : 0; + + public int MeshLightCount => MeshLights.Count; + + public int LightCount => LightHandleToLightListEntry.Count + MeshLightCount + EnvLightCount; + + private static PTLight CreateEnvironmentLight() + { + PTLight envLight; + envLight.type = ENVIRONMENT_LIGHT; + envLight.position = Vector3.zero; + envLight.intensity = Vector3.zero; + envLight.castShadows = 1; + envLight.contributesToDirectLighting = 1; + envLight.forward = Vector3.zero; + envLight.attenuation = Vector4.one; + envLight.up = Vector3.zero; + envLight.right = Vector3.zero; + envLight.width = 0; + envLight.height = 0; + envLight.spotAngle = 0; + envLight.innerSpotAngle = 0; + envLight.layerMask = uint.MaxValue; + envLight.indirectScale = 1.0f; + envLight.attenuation = Vector4.zero; + envLight.range = float.MaxValue; + envLight.shadowMaskChannel = -1; + envLight.falloffIndex = -1; + envLight.shadowRadius = 0.0f; + envLight.cookieIndex = -1; + return envLight; + } + + public void Build(Bounds sceneBounds, CommandBuffer cmdBuf, bool addEnvironmentLight) + { + // Make sure the light list is empty before we build it. + LightList.Clear(); + LightHandleToLightListIndex.Clear(); + + // Combine the mesh lights and the rest of the lights. + foreach (var light in LightHandleToLightListEntry) + { + LightHandleToLightListIndex.Add(light.Key, LightList.Count); + LightList.Add(light.Value); + } + foreach (var meshLight in MeshLights) + LightList.Add(meshLight.Value); + + // If we explicitly sample emitters, include the environment light in the light list + if (addEnvironmentLight) + { + LightList.Add(CreateEnvironmentLight()); + HasEnvironmentLight = true; + } + + BuildLightFalloffLUTs(this); + + SetLightDataOnCommandBuffer(this, cmdBuf); + } + + private static void BuildLightFalloffLUTs(LightState lightState) + { + // Build the LUT data + lightState.LightFalloff = LightFalloffLUT.BuildLightFalloffLUTs(lightState.LightFalloffDescs.ToArray(), LightFalloffLUTLength); + Debug.Assert(lightState.LightFalloff.Length == LightFalloffLUTLength * lightState.LightFalloffDescs.Count); + + // Store the range of each LUT + lightState.LightFalloffLUTRanges = new float[lightState.LightFalloffDescs.Count]; + int i = 0; + foreach (var desc in lightState.LightFalloffDescs) + { + Debug.Assert(desc.LUTRange >= 0.0f); + lightState.LightFalloffLUTRanges[i] = desc.LUTRange; + i++; + } + + // We have built the LUTs, now clear the descriptors so we don't rebuild them every frame + lightState.LightFalloffDescs.Clear(); + } + + private static void SetLightDataOnCommandBuffer(LightState lightState, CommandBuffer cmdBuf) + { + if (lightState.LightListBuffer == null || lightState.LightListBuffer.count < lightState.LightList.Count) + { + lightState.LightListBuffer?.Release(); + lightState.LightListBuffer = new ComputeBuffer(math.max(64, lightState.LightList.Count), Marshal.SizeOf()); + } + + if (lightState.LightList.Count > 0) + { + cmdBuf.SetBufferData(lightState.LightListBuffer, lightState.LightList, 0, 0, lightState.LightList.Count); + } + + if (lightState.LightFalloffBuffer == null || lightState.LightFalloffBuffer.count < lightState.LightFalloff.Length) + { + lightState.LightFalloffBuffer?.Release(); + int count = math.max(1, lightState.LightFalloff.Length); + int stride = sizeof(float); + lightState.LightFalloffBuffer = new ComputeBuffer(count, stride); + } + + if (lightState.LightFalloff.Length > 0) + { + cmdBuf.SetBufferData(lightState.LightFalloffBuffer, lightState.LightFalloff, 0, 0, lightState.LightFalloff.Length); + } + + if (lightState.LightFalloffLUTRangeBuffer == null || lightState.LightFalloffLUTRangeBuffer.count < lightState.LightFalloffLUTRanges.Length) + { + lightState.LightFalloffLUTRangeBuffer?.Release(); + int count = math.max(1, lightState.LightFalloffLUTRanges.Length); + int stride = sizeof(float); + lightState.LightFalloffLUTRangeBuffer = new ComputeBuffer(count, stride); + } + + if (lightState.LightFalloffLUTRanges.Length > 0) + { + cmdBuf.SetBufferData(lightState.LightFalloffLUTRangeBuffer, lightState.LightFalloffLUTRanges, 0, 0, lightState.LightFalloffLUTRanges.Length); + } + } + } + private LightState _lightState; + + public LightPickingMethod lightPickingMethod + { + set => _lightState.lightPickingMethod = value; + get => _lightState.lightPickingMethod; + } + + public int MaterialCount => _materialPool.MaterialCount; + + public int LightCount => NonMeshLightCount + MeshLightCount + EnvLightCount; + public int NonMeshLightCount => _lightState.LightHandleToLightListEntry.Count; + public int MeshLightCount => _lightState.MeshLights.Count; + public int EnvLightCount => _lightState.HasEnvironmentLight ? 1 : 0; + + public List LightList => _lightState.LightList; + public Dictionary LightHandleToLightListIndex => _lightState.LightHandleToLightListIndex; + public ComputeBuffer LightListBuffer => _lightState.LightListBuffer; + + public ComputeBuffer LightFalloffBuffer => _lightState.LightFalloffBuffer; + public ComputeBuffer LightFalloffLUTRangeBuffer => _lightState.LightFalloffLUTRangeBuffer; + public uint LightFalloffLUTLength => LightState.LightFalloffLUTLength; + + public int LightListHashCode + { + get + { + int lightHashCode = 0; + foreach (var light in _lightState.LightList) + lightHashCode = HashCode.Combine(lightHashCode, light.GetHashCode()); + return lightHashCode; + } + } + + // SubMesh dictionary (instance handle, emissive submesh list) + private readonly Dictionary> _subMeshIndices = new(); + + #endregion + + private MaterialPool _materialPool; + private AccelStructAdapter _rayTracingAccelerationStructure; + + // Skybox sampling + private CubemapRender _cubemapRender; + private EnvironmentImportanceSampling _environmentSampling; + private int _currentSkyboxHash; + + // Many light sampling + private RegirLightGrid _reservoirGrid; + private ConservativeLightGrid _conservativeLightGrid; + + public void Init(RayTracingContext ctx, WorldResourceSet worldResources) + { + _materialPool = new MaterialPool(worldResources.SetAlphaChannelShader, worldResources.BlitCubemap, worldResources.BlitGrayScaleCookie); + + var options = new AccelerationStructureOptions() + { + buildFlags = BuildFlags.None, // TODO: Consider whether to use BuildFlags.MinimizeMemory once https://jira.unity3d.com/browse/UUM-54575 is fixed. + }; + _rayTracingAccelerationStructure = new AccelStructAdapter(ctx.CreateAccelerationStructure(options), new GeometryPool(GeometryPoolDesc.NewDefault(), ctx.Resources.geometryPoolKernels, ctx.Resources.copyBuffer)); + + _lightState = new LightState(); + + _cubemapRender = new CubemapRender(worldResources.SkyBoxMesh, worldResources.SixFaceSkyBoxMesh); + _environmentSampling = new EnvironmentImportanceSampling(worldResources.PathTracingSkySamplingDataShader); + _reservoirGrid = new RegirLightGrid(worldResources.BuildLightGridShader); + _conservativeLightGrid = new ConservativeLightGrid(worldResources.BuildLightGridShader); + } + + private static float Luminance(Color color) + { + return color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f; + } + + public void SetEnvironmentMaterial(Material mat) + { + _cubemapRender.SetMaterial(mat); + } + + public ComputeBuffer GetMaterialListBuffer() + { + return _materialPool.MaterialBuffer; + } + + public RenderTexture GetMaterialAlbedoTextures() + { + return _materialPool.AlbedoTextures; + } + + public RenderTexture GetMaterialEmissionTextures() + { + return _materialPool.EmissionTextures; + } + + public RenderTexture GetMaterialTransmissionTextures() + { + return _materialPool.TransmissionTextures; + } + + public RenderTexture GetLightCookieTextures() + { + return _materialPool.LightCookieTextures; + } + + public RenderTexture GetLightCubemapTextures() + { + return _materialPool.LightCubemapTextures; + } + + public Texture GetEnvironmentTexture(CommandBuffer cmd, int resolution, out EnvironmentCDF environmentCDF) + { + Debug.Assert(_environmentSampling != null, "You should call World::Init() first"); + + var envTex = _cubemapRender.GetCubemap(resolution, out int skyHash); + if (_currentSkyboxHash != skyHash) + { + _environmentSampling.ComputeCDFBuffers(cmd, envTex); + _currentSkyboxHash = skyHash; + } + environmentCDF = _environmentSampling.GetSkyboxCDF(); + return envTex; + } + + public void BindLightAccelerationStructure(CommandBuffer cmd, IRayTracingShader shader) + { + // As we don't use shader variants, we need to bind buffers for all types of supported light sampling + // Some of them will be dummy (see the underlying implementation) + if (_lightState.lightPickingMethod == LightPickingMethod.Regir) + _reservoirGrid.Bind(cmd, shader); + else + _conservativeLightGrid.Bind(cmd, shader); + } + + public void Dispose() + { + _rayTracingAccelerationStructure?.Dispose(); + _materialPool?.Dispose(); + _subMeshIndices.Clear(); + _lightState.LightListBuffer?.Dispose(); + _lightState.LightFalloffBuffer?.Dispose(); + _lightState.LightFalloffLUTRangeBuffer?.Dispose(); + _lightState.LightFalloffDescs.Clear(); + _lightState.LightFalloff = null; + _lightState.LightList.Clear(); + _lightState.LightHandleToLightListEntry.Clear(); + _lightState.MeshLights.Clear(); + _cubemapRender?.Dispose(); + _environmentSampling?.Dispose(); + _reservoirGrid?.Dispose(); + _conservativeLightGrid?.Dispose(); + } + + public AccelStructAdapter GetAccelerationStructure() + { + return _rayTracingAccelerationStructure; + } + + public void NextFrame() + { + _rayTracingAccelerationStructure.NextFrame(); + } + + // Provide a unique hash for the renderer sub meshes + private static int GetSubMeshHash(InstanceHandle instance, int subMeshIndex) + { + return HashCode.Combine(instance.Value, subMeshIndex); + } + + public void RemoveInstance(InstanceHandle instance) + { + try + { + _rayTracingAccelerationStructure.RemoveInstance(instance.ToInt()); + _instanceHandleSet.Remove(instance); + RemoveEmissiveMeshes(instance); + } + catch (Exception e) + { + LogException("Failed to remove instance", e, instance.Value); + } + } + + private void RemoveEmissiveMeshes(InstanceHandle instance) + { + if (_subMeshIndices.ContainsKey(instance)) + { + var emissiveSubMeshes = _subMeshIndices[instance]; + foreach (var subMeshIndex in emissiveSubMeshes) + { + var subMeshHash = GetSubMeshHash(instance, subMeshIndex); + if (_lightState.MeshLights.ContainsKey(subMeshHash)) + _lightState.MeshLights.Remove(subMeshHash); + } + _subMeshIndices.Remove(instance); + } + } + + public void RemoveMaterial(MaterialHandle materialHandle) + { + try + { + _materialHandleSet.Remove(materialHandle); + _materialPool.RemoveMaterial(materialHandle.Value); + } + catch (Exception e) + { + LogException("failed to remove material", e, materialHandle.Value); + } + } + + public MaterialHandle AddMaterial(in MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) + { + MaterialHandle handle = _materialHandleSet.Add(); + try + { + _materialPool.AddMaterial(handle.Value, in material, albedoAndEmissionUVChannel); + } + catch (Exception e) + { + LogException("failed to add material", e, handle.Value); + } + return handle; + } + + public void UpdateMaterial(MaterialHandle materialHandle, in MaterialDescriptor material, UVChannel albedoAndEmissionUVChannel) + { + try + { + _materialPool.UpdateMaterial(materialHandle.Value, in material, albedoAndEmissionUVChannel); + } + catch (Exception e) + { + LogException("failed to modify material", e, materialHandle.Value); + } + } + + private void LogException(string message, Exception e, Object obj) + { + var objName = obj != null ? obj.name : "null"; + Debug.LogError($"PathTracing: {message} <{objName}> \n{e.Message}", obj); + } + + private void LogException(string message, Exception e, UInt64 instanceHandle) + { + Debug.LogError($"PathTracing: {message} <{instanceHandle}> \n{e.Message}"); + } + + private void LogError(string message) + { + Debug.LogError($"PathTracing: {message} \n"); + } + + public InstanceHandle AddInstance( + Mesh mesh, + Span materials, + Span masks, + uint renderingLayerMask, + in Matrix4x4 localToWorldMatrix, + Bounds bounds, + bool isStatic, + RenderedGameObjectsFilter filter, + bool enableEmissiveSampling) + { + Debug.Assert(mesh.subMeshCount == materials.Length); + Debug.Assert(mesh.subMeshCount == masks.Length); + + Span materialIndices = stackalloc uint[mesh.subMeshCount]; + Span isOpaque = stackalloc bool[mesh.subMeshCount]; + for (int i = 0; i < materials.Length; ++i) + { + if (materials[i] == MaterialHandle.Invalid) + continue; + + bool isTransmissive = false; + _materialPool.GetMaterialInfo(materials[i].Value, out materialIndices[i], out isTransmissive); + isOpaque[i] = !isTransmissive; + } + + InstanceHandle instance = _instanceHandleSet.Add(); + _rayTracingAccelerationStructure.AddInstance(instance.ToInt(), mesh, localToWorldMatrix, masks, materialIndices, isOpaque, renderingLayerMask); + + if (enableEmissiveSampling && !ProcessEmissiveMeshes(instance, mesh, bounds, materials, isStatic, _rayTracingAccelerationStructure, _materialPool, filter, _lightState.MeshLights, _subMeshIndices)) + LogError($"Failed to process emissive triangles in mesh {mesh.name}."); + + return instance; + } + + public void UpdateInstanceTransform(InstanceHandle instance, Matrix4x4 localToWorldMatrix) + { + _rayTracingAccelerationStructure.UpdateInstanceTransform(instance.ToInt(), localToWorldMatrix); + } + + public void UpdateInstanceMask(InstanceHandle instance, Span perSubMeshMask) + { + _rayTracingAccelerationStructure.UpdateInstanceMask(instance.ToInt(), perSubMeshMask); + } + public void UpdateInstanceMask(InstanceHandle instance, uint mask) + { + _rayTracingAccelerationStructure.UpdateInstanceMask(instance.ToInt(), mask); + } + + public void UpdateInstanceMaterials(InstanceHandle instance, Span materials) + { + Span materialIndices = stackalloc uint[materials.Length]; + for (int i = 0; i < materials.Length; ++i) + { + _materialPool.GetMaterialInfo(materials[i].Value, out materialIndices[i], out bool isTransmissive); + } + + _rayTracingAccelerationStructure.UpdateInstanceMaterialIDs(instance.ToInt(), materialIndices); + } + + public void UpdateInstanceEmission( + InstanceHandle instance, + Mesh mesh, + Bounds bounds, + Span materials, + bool isStatic, + RenderedGameObjectsFilter filter) + { + if (!ProcessEmissiveMeshes(instance, mesh, bounds, materials, isStatic, _rayTracingAccelerationStructure, _materialPool, filter, _lightState.MeshLights, _subMeshIndices)) + { + LogError($"failed to process emissive triangles in mesh with handle {instance}"); + } + } + + // InstanceMask bit encoding format: + internal static uint GetInstanceMask(ShadowCastingMode shadowMode, bool isStatic, RenderedGameObjectsFilter filter, bool hasLightmaps = true) + { + uint instanceMask = 0u; + + if (shadowMode != ShadowCastingMode.Off) + { + if (filter == RenderedGameObjectsFilter.All) + { + instanceMask |= (uint)InstanceFlags.SHADOW_RAY_VIS_MASK; + } + else + { + if (isStatic) + { + instanceMask |= (uint)InstanceFlags.SHADOW_RAY_VIS_MASK; + } + } + } + + if (shadowMode != ShadowCastingMode.ShadowsOnly) + { + if (filter == RenderedGameObjectsFilter.All) + { + instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; + instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; + } + else if (filter == RenderedGameObjectsFilter.OnlyStatic) + { + if (isStatic) + { + if (hasLightmaps) + { + instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; + } + instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; + } + } + else if (filter == RenderedGameObjectsFilter.AllInCameraRaysThenOnlyStatic) + { + if (isStatic) + { + instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; + instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; + } + else + { + instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; + } + } + } + + return instanceMask; + } + + private static bool ProcessEmissiveMeshes( + InstanceHandle instance, + Mesh mesh, + Bounds bounds, + Span materials, + bool isStatic, + AccelStructAdapter rtAccelStruct, + MaterialPool sceneMaterials, + RenderedGameObjectsFilter filter, + Dictionary meshLights, + Dictionary> subMeshIndexMap) + { + if (filter != RenderedGameObjectsFilter.All && !isStatic) + return true; + + int subMeshCount = mesh.subMeshCount; + + if (!rtAccelStruct.GetInstanceIDs(instance.ToInt(), out int[] instanceHandles)) + { + // This should never happen as long as the renderer was already added to the acceleration structure + return false; + } + + // Approximate area of the emissive mesh using the bounding box + float boundingBoxArea = 2 * (bounds.size.x * bounds.size.y) + + 2 * (bounds.size.y * bounds.size.z) + + 2 * (bounds.size.x * bounds.size.z); + + // List to keep track of the emissive subMeshes + List subMeshIndices = new List(subMeshCount); + for (int i = 0; i < subMeshCount; ++i) + { + MaterialHandle mat = materials[i]; + + // If it's an emissive material, create emissive triangles for this submesh + if (sceneMaterials.IsEmissive(mat.Value, out float3 emission)) + { + var triangleIndices = mesh.GetTriangles(i); + var triangleCount = triangleIndices.Length / 3; + + // create a new MeshLight entry + PTLight newLight; + { + newLight.type = EMISSIVE_MESH; + newLight.height = instanceHandles[i]; + newLight.attenuation = Vector4.one; + newLight.attenuation.x = triangleCount; // number of emissive triangles + newLight.intensity = emission; + newLight.position = Vector3.zero; + newLight.up = Vector3.zero; + newLight.right = Vector3.zero; + newLight.forward = Vector3.zero; + newLight.width = boundingBoxArea; + newLight.castShadows = 1; + newLight.contributesToDirectLighting = 1; + newLight.indirectScale = 1.0f; + newLight.spotAngle = 0; + newLight.innerSpotAngle = 0; + newLight.layerMask = uint.MaxValue; + newLight.range = float.MaxValue; + newLight.shadowMaskChannel = -1; + newLight.falloffIndex = -1; + newLight.shadowRadius = 0.0f; + newLight.cookieIndex = -1; // mesh lights sample directly the emission texture, they don't have a light cookie + } + + var subMeshHash = GetSubMeshHash(instance, i); + meshLights[subMeshHash] = newLight; + + // keep track of which subMeshes are emissive (need this for when removing meshes) + subMeshIndices.Add(i); + } + } + + if (subMeshIndices.Count > 0) + { + subMeshIndexMap[instance] = subMeshIndices; + } + return true; + } + + public LightHandle[] AddLights(Span lights, + bool respectLightLayers, + bool autoEstimateLUTRange, + MixedLightingMode mixedLightingMode) + { + // Generate handles + LightHandle[] handles = new LightHandle[lights.Length]; + for (int i = 0; i < lights.Length; i++) + { + LightHandle handle = _lightState.LightHandleSet.Add(); + handles[i] = handle; + } + + // Use the handles to update the lights for the first time + UpdateLights(handles, lights, respectLightLayers, autoEstimateLUTRange, mixedLightingMode); + + return handles; + } + + private static float EstimateLUTRange(float range, float luminance, Experimental.GlobalIllumination.FalloffType falloffType, float threshold = 0.01f) + { + Debug.Assert(threshold > 0.0f); + Debug.Assert(range > 0.0f); + if (luminance <= 0.0f) + return 1.0f; + + switch (falloffType) + { + case Experimental.GlobalIllumination.FalloffType.InverseSquaredNoRangeAttenuation: + case Experimental.GlobalIllumination.FalloffType.InverseSquared: + { + // compute the range at which the attenuated luminance is below the threshold + float estimatedRange = math.max(1.0f, math.ceil(math.sqrt(luminance / threshold))); + Debug.Assert(luminance * LightFalloffLUT.InverseSquaredFalloff(estimatedRange * estimatedRange) <= threshold); + return math.min(estimatedRange, range); + } + case Experimental.GlobalIllumination.FalloffType.Linear: + case Experimental.GlobalIllumination.FalloffType.Legacy: + return range; + } + + return range; + } + + public void UpdateLights(LightHandle[] lights, Span lightDescriptors, + bool respectLightLayers, + bool autoEstimateLUTRange, + MixedLightingMode mixedLightingMode) + { + Debug.Assert(lights.Length == lightDescriptors.Length); + + Dictionary falloffHashToFalloffIndex = new(); + int falloffIndex = 0; + + // Convert the lights. + for (int i = 0; i < lights.Length; i++) + { + ref readonly LightDescriptor light = ref lightDescriptors[i]; + + PTLight newLight; + newLight.position = light.Transform.GetPosition(); + newLight.intensity = light.LinearLightColor; + newLight.type = (int)light.Type; + newLight.castShadows = light.Shadows != LightShadows.None ? 1 : 0; + newLight.forward = (light.Transform.rotation * Vector3.forward).normalized; + newLight.attenuation = Vector4.one; + newLight.up = (light.Transform.rotation * Vector3.up).normalized; + newLight.right = (light.Transform.rotation * Vector3.right).normalized; + newLight.width = light.AreaSize.x; + newLight.height = light.AreaSize.y; + newLight.spotAngle = light.SpotAngle; + newLight.innerSpotAngle = light.InnerSpotAngle; + newLight.layerMask = respectLightLayers ? light.CullingMask : uint.MaxValue; + newLight.indirectScale = light.BounceIntensity; + newLight.range = light.Range; + newLight.shadowRadius = light.ShadowRadius; + newLight.shadowMaskChannel = light.ShadowMaskChannel; + + switch (light.LightmapBakeType) + { + case LightmapBakeType.Baked: + case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.Subtractive: + newLight.contributesToDirectLighting = 1; + break; + case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.Shadowmask: + // Fallback to baked behavior if we don't have a valid shadowmask channel + newLight.contributesToDirectLighting = newLight.shadowMaskChannel != -1 ? 0 : 1; + break; + case LightmapBakeType.Mixed when mixedLightingMode == MixedLightingMode.IndirectOnly: + case LightmapBakeType.Realtime: + default: + newLight.contributesToDirectLighting = 0; + break; + } + + if (light.Type == LightType.Spot) + { + // aspect ratio is serialized in areaSize.x + float aspect = light.AreaSize.x; + float frustumHeight = 2.0f * Mathf.Tan(light.SpotAngle * 0.5f * Mathf.Deg2Rad); + float frustumWidth = frustumHeight * aspect; + newLight.width = frustumWidth; + newLight.height = frustumHeight; + } + + if (light.Type == LightType.Spot || light.Type == LightType.Point) + { + Debug.Assert(light.FalloffType != Experimental.GlobalIllumination.FalloffType.Undefined); + + float estimatedLUTRange = light.Range; + if (autoEstimateLUTRange) + { + // Guesstimate a good LUT range, such that the LUT covers the falloff up to a distance where it is nearly 0. + // The range can be any number and we don't want to stretch the LUT to some arbitrary range (most of which is practically 0). + estimatedLUTRange = EstimateLUTRange(light.Range, Luminance(new Color(light.LinearLightColor.x, light.LinearLightColor.y, light.LinearLightColor.z, 1.0f)), light.FalloffType, 0.01f); + } + + LightFalloffDesc falloffDesc = new LightFalloffDesc + { + LUTRange = estimatedLUTRange, + FalloffType = light.FalloffType + }; + var falloffHash = falloffDesc.GetHashCode(); + if (!falloffHashToFalloffIndex.TryGetValue(falloffHash, out newLight.falloffIndex)) + { + // Add new falloff entry + newLight.falloffIndex = falloffIndex++; + falloffHashToFalloffIndex.Add(falloffHash, newLight.falloffIndex); + _lightState.LightFalloffDescs.Add(falloffDesc); + } + } + else + newLight.falloffIndex = -1; + +#pragma warning disable 162 // Disable unreachable code warning + + // Light attenuation parameters and math from HDRP + const bool applyRangeAttenuation = true; + if (applyRangeAttenuation) + { + newLight.attenuation.x = 1.0f / (light.Range * light.Range); + newLight.attenuation.y = 1.0f; + } + else + { + const float hugeValue = 16777216.0f; + const float sqrtHuge = 4096.0f; + newLight.attenuation.x = sqrtHuge / (light.Range * light.Range); + newLight.attenuation.y = hugeValue; + } +#pragma warning restore 162 + + newLight.attenuation.z = 0.0f; + if (light.Type == LightType.Spot) + { + var spotAngle = light.SpotAngle; + + var cosSpotOuterHalfAngle = Mathf.Clamp(Mathf.Cos(spotAngle * 0.5f * Mathf.Deg2Rad), 0.0f, 1.0f); + var cosSpotInnerHalfAngle = Mathf.Clamp(Mathf.Cos(0.5f * light.InnerSpotAngle * Mathf.Deg2Rad), 0.0f, 1.0f); // inner cone + + var val = Mathf.Max(0.0001f, (cosSpotInnerHalfAngle - cosSpotOuterHalfAngle)); + newLight.attenuation.z = 1.0f / val; + newLight.attenuation.w = -cosSpotOuterHalfAngle * newLight.attenuation.z; + } + + newLight.cookieIndex = _materialPool.AddCookieTexture(light.CookieTexture); + + _lightState.LightHandleToLightListEntry[lights[i]] = newLight; + } + } + + public void RemoveLights(Span lights) + { + foreach (var light in lights) + { + if (_lightState.LightHandleToLightListEntry.TryGetValue(light, out var ptLight)) + { + var cookieFaces = (ptLight.type == (int)LightType.Point) ? 6 : 1; + _materialPool.RemoveCookieTexture(cookieFaces, ptLight.cookieIndex); + } + + _lightState.LightHandleSet.Remove(light); + _lightState.LightHandleToLightListEntry.Remove(light); + } + } + + public void Build(Bounds sceneBounds, CommandBuffer cmdBuf, ref GraphicsBuffer scratchBuffer, Rendering.Sampling.SamplingResources samplingResources, bool emissiveSampling) + { + Debug.Assert(_rayTracingAccelerationStructure != null); + _lightState.Build(sceneBounds, cmdBuf, emissiveSampling && _cubemapRender.GetMaterial() != null); + + if (_lightState.lightPickingMethod == LightPickingMethod.Regir) + { + _reservoirGrid.Build(cmdBuf, _lightState, sceneBounds, samplingResources); + } + else if (_lightState.lightPickingMethod == LightPickingMethod.LightGrid) + { + _conservativeLightGrid.Build(cmdBuf, _lightState, sceneBounds, samplingResources); + } + + _materialPool.Build(cmdBuf); + _rayTracingAccelerationStructure.Build(cmdBuf, ref scratchBuffer); + } + + public UInt64 GetInstanceHandles(InstanceHandle handle) + { + int[] ids; + _rayTracingAccelerationStructure.GetInstanceIDs(handle.ToInt(), out ids); + return (UInt64)ids[0]; + } + + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs.meta new file mode 100644 index 00000000000..5461200e721 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/World.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 60471402671449019d33e895c0e4f5cf +timeCreated: 1693489662 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs new file mode 100644 index 00000000000..b422d6c979e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs @@ -0,0 +1,140 @@ +using System; +using UnityEngine.Rendering; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityEngine.PathTracing.Core +{ + [Serializable] + [SupportedOnRenderPipeline()] + [Categorization.CategoryInfo(Name = "R: Path Tracing Core World", Order = 1000), HideInInspector] + internal class WorldRenderPipelineResources : IRenderPipelineResources + { + [SerializeField, HideInInspector] int _version = 3; + + public int version + { + get => _version; + } + + [SerializeField, ResourcePath("Runtime/PathTracing/Shaders/BlitCubemap.compute")] + ComputeShader _blitCubemap; + + [SerializeField, ResourcePath("Runtime/PathTracing/Shaders/BlitCookie.compute")] + ComputeShader _blitGrayScaleCookie; + + [SerializeField, ResourcePath("Runtime/PathTracing/Shaders/SetAlphaChannel.compute")] + ComputeShader _setAlphaChannelShader; + + [SerializeField, ResourcePath("Runtime/PathTracing/Shaders/PathTracingSkySamplingData.compute")] + ComputeShader _pathTracingSkySamplingDataShader; + + [SerializeField, ResourcePath("Runtime/PathTracing/Meshes/SkyBoxMesh.mesh")] + Mesh _skyBoxMesh; + + [SerializeField, ResourcePath("Runtime/PathTracing/Meshes/6FaceSkyboxMesh.mesh")] + Mesh _sixFaceSkyBoxMesh; + + [SerializeField, ResourcePath("Runtime/PathTracing/Shaders/BuildLightGrid.compute")] + ComputeShader _buildLightGridShader; + + + public ComputeShader BlitCubemap + { + get => _blitCubemap; + set => this.SetValueAndNotify(ref _blitCubemap, value, nameof(_blitCubemap)); + } + + public ComputeShader BlitGrayScaleCookie + { + get => _blitGrayScaleCookie; + set => this.SetValueAndNotify(ref _blitGrayScaleCookie, value, nameof(_blitGrayScaleCookie)); + } + + public ComputeShader SetAlphaChannelShader + { + get => _setAlphaChannelShader; + set => this.SetValueAndNotify(ref _setAlphaChannelShader, value, nameof(_setAlphaChannelShader)); + } + + public ComputeShader PathTracingSkySamplingDataShader + { + get => _pathTracingSkySamplingDataShader; + set => this.SetValueAndNotify(ref _pathTracingSkySamplingDataShader, value, nameof(_pathTracingSkySamplingDataShader)); + } + + public Mesh SkyBoxMesh + { + get => _skyBoxMesh; + set => this.SetValueAndNotify(ref _skyBoxMesh, value, nameof(_skyBoxMesh)); + } + + public Mesh SixFaceSkyBoxMesh + { + get => _sixFaceSkyBoxMesh; + set => this.SetValueAndNotify(ref _sixFaceSkyBoxMesh, value, nameof(_sixFaceSkyBoxMesh)); + } + + public ComputeShader BuildLightGridShader + { + get => _buildLightGridShader; + set => this.SetValueAndNotify(ref _buildLightGridShader, value, nameof(_buildLightGridShader)); + } + } + + internal class WorldResourceSet + { + public ComputeShader BlitCubemap; + public ComputeShader BlitGrayScaleCookie; + public ComputeShader SetAlphaChannelShader; + public ComputeShader PathTracingSkySamplingDataShader; + public Mesh SkyBoxMesh; + public Mesh SixFaceSkyBoxMesh; + public ComputeShader BuildLightGridShader; + +#if UNITY_EDITOR + public void LoadFromAssetDatabase() + { + const string packageFolder = "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/"; + + BlitCubemap = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/BlitCubemap.compute"); + BlitGrayScaleCookie = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/BlitCookie.compute"); + SetAlphaChannelShader = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/SetAlphaChannel.compute"); + PathTracingSkySamplingDataShader = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/PathTracingSkySamplingData.compute"); + SkyBoxMesh = AssetDatabase.LoadAssetAtPath(packageFolder + "Meshes/SkyboxMesh.mesh"); + SixFaceSkyBoxMesh = AssetDatabase.LoadAssetAtPath(packageFolder + "Meshes/6FaceSkyboxMesh.mesh"); + BuildLightGridShader = AssetDatabase.LoadAssetAtPath(packageFolder + "Shaders/BuildLightGrid.compute"); + } +#endif + + public bool LoadFromRenderPipelineResources() + { + if (GraphicsSettings.TryGetRenderPipelineSettings(out var rpResources)) + { + Debug.Assert(rpResources.BlitCubemap != null); + Debug.Assert(rpResources.BlitGrayScaleCookie != null); + Debug.Assert(rpResources.SetAlphaChannelShader != null); + Debug.Assert(rpResources.PathTracingSkySamplingDataShader != null); + Debug.Assert(rpResources.SkyBoxMesh != null); + Debug.Assert(rpResources.SixFaceSkyBoxMesh != null); + Debug.Assert(rpResources.BuildLightGridShader != null); + + BlitCubemap = rpResources.BlitCubemap; + BlitGrayScaleCookie = rpResources.BlitGrayScaleCookie; + SetAlphaChannelShader = rpResources.SetAlphaChannelShader; + PathTracingSkySamplingDataShader = rpResources.PathTracingSkySamplingDataShader; + SkyBoxMesh = rpResources.SkyBoxMesh; + SixFaceSkyBoxMesh = rpResources.SixFaceSkyBoxMesh; + BuildLightGridShader = rpResources.BuildLightGridShader; + + return true; + } + else + { + return false; + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs.meta b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs.meta new file mode 100644 index 00000000000..c84c461e04e --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Runtime/PathTracing/WorldResources.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 64cd2972695546359b063097f682f9da +timeCreated: 1736516171 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/CompilerContextData.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/CompilerContextData.cs index 3829ae79dbe..d408644c86c 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/CompilerContextData.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/CompilerContextData.cs @@ -247,6 +247,19 @@ public void CullAllPasses(bool isCulled) } } + public TextureUVOrigin GetTextureUVOrigin(in TextureHandle targetHandle) + { + if (targetHandle.handle.IsValid()) + { + ref readonly ResourceUnversionedData unversionedData = ref UnversionedResourceData(targetHandle.handle); + return unversionedData.textureUVOrigin == TextureUVOriginSelection.TopLeft ? TextureUVOrigin.TopLeft : TextureUVOrigin.BottomLeft; + } + else + { + return TextureUVOrigin.BottomLeft; + } + } + // Helper to loop over native passes public ref struct NativePassIterator { diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.Debug.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.Debug.cs index 317482599ee..f700f43386c 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.Debug.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.Debug.cs @@ -76,7 +76,10 @@ internal static string MakePassMergeMessage(CompilerContextData ctx, in PassData $"- {passName}: {pass.fragmentInfoWidth}x{pass.fragmentInfoHeight}, {pass.fragmentInfoSamples} sample(s)."; break; case PassBreakReason.NextPassReadsTexture: - message += "The next pass reads one of the outputs as a regular texture, the pass needs to break."; + message += $"{prevPassName} output is sampled by {passName} as a regular texture, the pass needs to break."; + break; + case PassBreakReason.NextPassTargetsTexture: + message += $"{prevPassName} reads a texture that {passName} targets to, the pass needs to break."; break; case PassBreakReason.NonRasterPass: message += $"{prevPassName} is type {prevPass.type}. Only Raster passes can be merged."; diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs index de2cd05a757..de9364a4ccb 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -using UnityEngine.Rendering; namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler { @@ -16,13 +15,15 @@ internal struct RenderGraphInputInfo public string debugName; public bool disablePassCulling; public bool disablePassMerging; + public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy; } internal RenderGraphInputInfo graph; internal CompilerContextData contextData = null; internal CompilerContextData defaultContextData; internal CommandBuffer previousCommandBuffer; - Stack toVisitPassIds; + Stack m_HasSideEffectPassIdCullingStack; + List> m_UnusedVersionedResourceIdCullingStacks; RenderGraphCompilationCache m_CompilationCache; @@ -39,7 +40,11 @@ public NativePassCompiler(RenderGraphCompilationCache cache) { m_CompilationCache = cache; defaultContextData = new CompilerContextData(); - toVisitPassIds = new Stack(k_EstimatedPassCount); + m_HasSideEffectPassIdCullingStack = new Stack(k_EstimatedPassCount); + + m_UnusedVersionedResourceIdCullingStacks = new List>(); + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + m_UnusedVersionedResourceIdCullingStacks.Add(new Stack()); m_TempMRTArrays = new RenderTargetIdentifier[RenderGraph.kMaxMRTCount][]; for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i) @@ -69,7 +74,7 @@ public void Cleanup() } public bool Initialize(RenderGraphResourceRegistry resources, List renderPasses, RenderGraphDebugParams debugParams, string debugName, bool useCompilationCaching, - int graphHash, int frameIndex) + int graphHash, int frameIndex, RenderTextureUVOriginStrategy renderTextureUVOriginStrategy) { bool cached = false; if (!useCompilationCaching) @@ -82,6 +87,7 @@ public bool Initialize(RenderGraphResourceRegistry resources, List= 0; passIndex--) { ref readonly var pass = ref ctx.passData.ElementAt(passIndex); // Remove the connections from the list so they won't be visited again if (pass.culled) { - // If the culled pass was supposed to generate the latest version of a given resource, - // we need to decrement the latestVersionNumber of this resource - // because its last version will never be created due to its producer being culled - foreach (ref readonly var output in pass.Outputs(ctx)) - { - var outputResource = output.resource; - bool isOutputLastVersion = (outputResource.version == ctx.UnversionedResourceData(outputResource).latestVersionNumber); + pass.DisconnectFromResources(ctx); + } + } - if (isOutputLastVersion) - ctx.UnversionedResourceData(outputResource).latestVersionNumber--; - } + // Second step of the algorithm, must come after + CullRenderGraphPassesWritingOnlyUnusedResources(); + } + } + + void CullRenderGraphPassesWritingOnlyUnusedResources() + { + var ctx = contextData; + + var numPasses = ctx.passData.Length; + for (int passIndex = 0; passIndex < numPasses; passIndex++) + { + ref var passData = ref ctx.passData.ElementAt(passIndex); + + // Use the generic tag to monitor the number of written resources that are used + passData.tag = passData.numOutputs; + + // Find all resources that are written by a pass but not read at all and add them to the stacks + foreach (ref readonly var output in passData.Outputs(ctx)) + { + ref readonly var outputResource = ref output.resource; + ref var outputVersionedDataRes = ref ctx.resources[outputResource]; + + if (outputVersionedDataRes.numReaders == 0) + m_UnusedVersionedResourceIdCullingStacks[outputResource.iType].Push(outputResource); + } + } + + // Go through each stack of unused resources and try to cull their producer + for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) + { + var unusedVersionedResourceIdCullingStack = m_UnusedVersionedResourceIdCullingStacks[type]; + + // Goal is to find the producers of the unused resources and culled them if they only write to unused resources + while (unusedVersionedResourceIdCullingStack.Count != 0) + { + var unusedResource = unusedVersionedResourceIdCullingStack.Pop(); + + ref var unusedUnversionedDataRes = ref ctx.resources.unversionedData[type].ElementAt(unusedResource.index); + if (unusedUnversionedDataRes.isImported) continue; // Not always unused as someone can read it outside the graph + + ref var unusedVersionedDataRes = ref ctx.resources[unusedResource]; + ref var producerData = ref ctx.passData.ElementAt(unusedVersionedDataRes.writePassId); + if (producerData.culled) continue; // Producer has been culled already + + // Decrement the number of written resources that are used for this pass + producerData.tag--; + + Debug.Assert(producerData.tag >= 0); + + // Producer is not necessary anymore, as it only writes to unused resources and has no side effects + if (producerData.tag == 0 && !producerData.hasSideEffects) + { + producerData.culled = true; + producerData.DisconnectFromResources(ctx, unusedVersionedResourceIdCullingStack, type); + } + else // Producer is (still) necessary, but we might need to remove the read of the previous version coming implicitly with the write of the current version + { + // We always add written resource to the stack so versionedIndex > 0 + var prevVersionedRes = new ResourceHandle(unusedResource, unusedResource.version - 1); - // Notifying the versioned resources that this pass is no longer reading them - foreach (ref readonly var input in pass.Inputs(ctx)) + // If no explicit read is requested by the user (AccessFlag.Write only), we need to remove the implicit read + // so that we cut cleanly the connection between previous version of the resource and current producer + bool isImplicitRead = graph.m_RenderPasses[producerData.passId].implicitReadsList.Contains(prevVersionedRes); + + if (isImplicitRead) { - var inputResource = input.resource; - ctx.resources[inputResource].RemoveReadingPass(ctx, inputResource, pass.passId); + ref var prevVersionedDataRes = ref ctx.resources[prevVersionedRes]; + + // Notify the previous version of this resource that it is not read anymore by this pass + prevVersionedDataRes.RemoveReadingPass(ctx, prevVersionedRes, producerData.passId); + + // We also need to add the previous version of the resource to the stack IF no other pass than current producer needed it + if (prevVersionedDataRes.written && prevVersionedDataRes.numReaders == 0) + { + unusedVersionedResourceIdCullingStack.Push(prevVersionedRes); + } } } } @@ -618,18 +695,15 @@ void FindResourceUsageRanges() ref var pointTo = ref ctx.UnversionedResourceData(inputResource); pointTo.lastUsePassID = -1; - // If we use version 0 and nobody else is using it yet, + // If nobody else is using it yet, // mark this pass as the first using the resource. // It can happen that two passes use v0, e.g.: // pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0 // pass2.UseTex(v0,Read) -> "reads" v0 - if (inputResource.version == 0) + if (pointTo.firstUsePassID < 0) { - if (pointTo.firstUsePassID < 0) - { - pointTo.firstUsePassID = pass.passId; - pass.AddFirstUse(inputResource, ctx); - } + pointTo.firstUsePassID = pass.passId; + pass.AddFirstUse(inputResource, ctx); } // This pass uses the last version of a resource increase the ref count of this resource @@ -647,18 +721,16 @@ void FindResourceUsageRanges() { var outputResource = output.resource; ref var pointTo = ref ctx.UnversionedResourceData(outputResource); - if (outputResource.version == 1) + + // If nobody else is using it yet (no explicit read), + // Mark this pass as the first using the resource. + // It can happen that two passes use v0, e.g.: + // pass1.UseTex(v0, Write) -> implicit read of v0, writes v1 - culled because none explicitly reads v1 + // pass3.UseTex(v1, Write) -> implicit read of v1, writes v2 - not culled because of unrelated reason + if (pointTo.firstUsePassID < 0) { - // If we use version 0 and nobody else is using it yet, - // Mark this pass as the first using the resource. - // It can happen that two passes use v0, e.g.: - // pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0 - // pass3.UseTex(v0,Read/Write) -> wites v0, brings it to v1 from here on - if (pointTo.firstUsePassID < 0) - { - pointTo.firstUsePassID = pass.passId; - pass.AddFirstUse(outputResource, ctx); - } + pointTo.firstUsePassID = pass.passId; + pass.AddFirstUse(outputResource, ctx); } // This pass outputs the last version of a resource track that @@ -756,6 +828,62 @@ void PrepareNativeRenderPasses() } } + void PropagateTextureUVOrigin() + { + using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_PropagateTextureUVOrigin))) + { + // Work backwards through the native pass list and propagate the texture uv origin we store with to + // any texture attachments that are not explicitly known (usually intermediate memoryless attachments). + for (int passIdx = contextData.nativePassData.Length - 1; passIdx >= 0; --passIdx) + { + ref NativePassData nativePassData = ref contextData.nativePassData.ElementAt(passIdx); + + // Find a texture attachment that is storing to find out the orientation for this pass. + int attachmentsCount = nativePassData.attachments.size; + int firstStoreAttachmentIndex = 0; + TextureUVOriginSelection storeUVOrigin = TextureUVOriginSelection.Unknown; + for (int attIdx = 0; attIdx < attachmentsCount; ++attIdx) + { + ref NativePassAttachment nativePassAttachment = ref nativePassData.attachments[attIdx]; + if (nativePassAttachment.storeAction != RenderBufferStoreAction.DontCare) + { + if (nativePassAttachment.handle.type == RenderGraphResourceType.Texture) // Only textures have orientation + { + ref ResourceUnversionedData resData = ref contextData.UnversionedResourceData(nativePassAttachment.handle); + storeUVOrigin = resData.textureUVOrigin; // Inherit the orientation of the store if we are currently storing to an unknown orientation. + firstStoreAttachmentIndex = attIdx; + break; + } + } + } + + // Update any texture attachments with an unknown uv origin to the one we are going to use for storing and validate + // we don't have a mixture of uv origins on the texture attachment list as this would mean something is going to be + // read/written upside down. + for (int attIdx = 0; attIdx < attachmentsCount; ++attIdx) + { + ref NativePassAttachment nativePassAttachment = ref nativePassData.attachments[attIdx]; + + if (nativePassAttachment.handle.type == RenderGraphResourceType.Texture) + { + ref ResourceUnversionedData resData = ref contextData.UnversionedResourceData(nativePassAttachment.handle); + if (storeUVOrigin != TextureUVOriginSelection.Unknown && resData.textureUVOrigin != TextureUVOriginSelection.Unknown && resData.textureUVOrigin != storeUVOrigin) + { + ref NativePassAttachment firstStoreNativePassAttachment = ref nativePassData.attachments[firstStoreAttachmentIndex]; + var firstStoreAttachmentName = graph.m_ResourcesForDebugOnly.GetRenderGraphResourceName(firstStoreNativePassAttachment.handle); + var name = graph.m_ResourcesForDebugOnly.GetRenderGraphResourceName(nativePassAttachment.handle); + + throw new InvalidOperationException($"From pass '{contextData.passNames[nativePassData.firstGraphPass]}' to pass '{contextData.passNames[nativePassData.lastGraphPass]}' when trying to store resource '{name}' of type {nativePassAttachment.handle.type} at index {nativePassAttachment.handle.index} - " + + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginStore(firstStoreAttachmentName, storeUVOrigin, name, resData.textureUVOrigin)); + } + + resData.textureUVOrigin = storeUVOrigin; + } + } + } + } + } + static bool IsGlobalTextureInPass(RenderGraphPass pass, ResourceHandle handle) { foreach (var g in pass.setGlobalsList) @@ -773,6 +901,10 @@ void DetectMemoryLessResources() { using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_DetectMemorylessResources))) { + // No need to go further if we don't support memoryless textures + if (!SystemInfo.supportsMemorylessTextures) + return; + // Native renderpasses and create/destroy lists have now been set-up. Detect memoryless resources, i.e resources that are created/destroyed // within the scope of an nrp foreach (ref readonly var nativePass in contextData.NativePasses) diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/PassesData.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/PassesData.cs index 9087f155fbb..3d899b047b5 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/PassesData.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/PassesData.cs @@ -442,6 +442,35 @@ internal readonly bool IsUsedAsFragment(ResourceHandle h, CompilerContextData ct return false; } + + internal void DisconnectFromResources(CompilerContextData ctx, Stack unusedVersionedResourceIdCullingStack = null, int type = 0) + { + // If the culled pass was supposed to generate the latest version of a given resource, + // we need to decrement the latestVersionNumber of this resource + // because its last version will never be created due to its producer being culled + foreach (ref readonly var output in Outputs(ctx)) + { + ref readonly var outputResource = ref output.resource; + bool isOutputLastVersion = (outputResource.version == ctx.UnversionedResourceData(outputResource).latestVersionNumber); + + if (isOutputLastVersion) + ctx.UnversionedResourceData(outputResource).latestVersionNumber--; + } + + // Notifying the versioned resources that this pass is no longer reading them + foreach (ref readonly var input in Inputs(ctx)) + { + ref readonly var inputResource = ref input.resource; + ref var inputVersionedDataResource = ref ctx.resources[inputResource]; + inputVersionedDataResource.RemoveReadingPass(ctx, inputResource, passId); + + // If a resource of the same type is not used anymore, adding it to the stack + if (unusedVersionedResourceIdCullingStack != null && inputResource.iType == type && inputVersionedDataResource.written && inputVersionedDataResource.numReaders == 0) + { + unusedVersionedResourceIdCullingStack.Push(inputResource); + } + } + } } // Data per attachment of a native renderpass @@ -555,6 +584,7 @@ internal enum PassBreakReason NotOptimized, // Optimize never ran on this pass TargetSizeMismatch, // Target Sizes or msaa samples don't match NextPassReadsTexture, // The next pass reads data written by this pass as a texture + NextPassTargetsTexture, // The next pass targets the texture that this pass is reading NonRasterPass, // The next pass is a non-raster pass DifferentDepthTextures, // The next pass uses a different depth texture (and we only allow one in a whole NRP) AttachmentLimitReached, // Adding the next pass would have used more attachments than allowed @@ -592,6 +622,7 @@ public PassBreakAudit(PassBreakReason reason, int breakPass) "The native render pass optimizer never ran on this pass. Pass is standalone and not merged.", "The render target sizes of the next pass do not match.", "The next pass reads data output by this pass as a regular texture.", + "The next pass uses a texture sampled in this pass as a render target.", "The next pass is not a raster render pass.", "The next pass uses a different depth buffer. All passes in the native render pass need to use the same depth buffer.", $"The limit of {FixedAttachmentArray.MaxAttachments} native pass attachments would be exceeded when merging with the next pass.", @@ -926,6 +957,24 @@ public static PassBreakAudit CanMerge(CompilerContextData contextData, int activ currAvailableAttachmentSlots--; } } + + // Check if this fragment is already sampled in the native renderpass not as a fragment but as an input + for (int i = nativePass.firstGraphPass; i <= nativePass.lastGraphPass; ++i) + { + ref var earlierPassData = ref contextData.passData.ElementAt(i); + foreach (ref readonly var earlierInput in earlierPassData.Inputs(contextData)) + { + // If this fragment is already used in current native render pass + if (earlierInput.resource.index == fragment.resource.index) + { + // If it's not used as a fragment, it's used as some sort of texture read of load so we need to sync it out + if (!earlierPassData.IsUsedAsFragment(earlierInput.resource, contextData)) + { + return new PassBreakAudit(PassBreakReason.NextPassTargetsTexture, passIdToMerge); + } + } + } + } } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs index 68044da6fe8..83ecf3d8025 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs @@ -36,10 +36,12 @@ internal struct ResourceUnversionedData public readonly bool discard; // graph.m_Resources.GetTextureResourceDesc(fragment.resource).discardBuffer; public readonly bool bindMS; + public TextureUVOriginSelection textureUVOrigin; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetName(CompilerContextData ctx, ResourceHandle h) => ctx.GetResourceName(h); - public ResourceUnversionedData(IRenderGraphResource rll, ref RenderTargetInfo info, ref TextureDesc desc, bool isResourceShared) + public ResourceUnversionedData(TextureResource rll, ref RenderTargetInfo info, ref TextureDesc desc, bool isResourceShared) { isImported = rll.imported; isShared = isResourceShared; @@ -59,6 +61,7 @@ public ResourceUnversionedData(IRenderGraphResource rll, ref RenderTargetInfo in clear = desc.clearBuffer; discard = desc.discardBuffer; bindMS = info.bindMS; + textureUVOrigin = rll.textureUVOrigin; } public ResourceUnversionedData(IRenderGraphResource rll, ref BufferDesc _, bool isResourceShared) @@ -83,6 +86,7 @@ public ResourceUnversionedData(IRenderGraphResource rll, ref BufferDesc _, bool clear = false; discard = false; bindMS = false; + textureUVOrigin = TextureUVOriginSelection.Unknown; } public ResourceUnversionedData(IRenderGraphResource rll, ref RayTracingAccelerationStructureDesc _, bool isResourceShared) @@ -107,6 +111,7 @@ public ResourceUnversionedData(IRenderGraphResource rll, ref RayTracingAccelerat clear = false; discard = false; bindMS = false; + textureUVOrigin = TextureUVOriginSelection.Unknown; } public void InitializeNullResource() @@ -114,6 +119,7 @@ public void InitializeNullResource() firstUsePassID = -1; lastUsePassID = -1; lastWritePassID = -1; + textureUVOrigin = TextureUVOriginSelection.Unknown; } } @@ -276,11 +282,12 @@ public void Initialize(RenderGraphResourceRegistry resources) { case (int)RenderGraphResourceType.Texture: { + var tex = rll as TextureResource; resources.GetRenderTargetInfo(h, out var info); - ref var desc = ref (rll as TextureResource).desc; + ref var desc = ref tex.desc; bool isResourceShared = resources.IsRenderGraphResourceShared(h); - unversionedData[t][r] = new ResourceUnversionedData(rll, ref info, ref desc, isResourceShared); + unversionedData[t][r] = new ResourceUnversionedData(tex, ref info, ref desc, isResourceShared); break; } case (int)RenderGraphResourceType.Buffer: diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.Compiler.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.Compiler.cs index 9a49da1624d..1c44fae3e45 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.Compiler.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.Compiler.cs @@ -15,7 +15,7 @@ internal NativePassCompiler CompileNativeRenderGraph(int graphHash) if (nativeCompiler == null) nativeCompiler = new NativePassCompiler(m_CompilationCache); - bool compilationIsCached = nativeCompiler.Initialize(m_Resources, m_RenderPasses, m_DebugParameters, name, m_EnableCompilationCaching, graphHash, m_ExecutionCount); + bool compilationIsCached = nativeCompiler.Initialize(m_Resources, m_RenderPasses, m_DebugParameters, name, m_EnableCompilationCaching, graphHash, m_ExecutionCount, m_renderTextureUVOriginStrategy); if (!compilationIsCached) nativeCompiler.Compile(m_Resources); diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.ExceptionMessages.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.ExceptionMessages.cs index 64e047487eb..76643e7a79f 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.ExceptionMessages.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.ExceptionMessages.cs @@ -113,6 +113,12 @@ internal static string UseDepthWithColorFormat(GraphicsFormat colorFormat) => internal static string UseTransientTextureInWrongPass(int transientIndex) => $"This pass is using a transient resource from a different pass (pass index {transientIndex}). A transient resource should only be used in a single pass."; + internal static string IncompatibleTextureUVOrigin(TextureUVOriginSelection origin, string attachmentType, string attachmentName, RenderGraphResourceType attachmentResourceType, int attachmentResourceIndex, TextureUVOriginSelection attachmentOrigin) => + $"TextureUVOrigin `{origin}` is not compatible with existing {attachmentType} attachment `{attachmentType}` of type `{attachmentResourceType}` at index `{attachmentResourceIndex}` with TextureUVOrigin `{attachmentOrigin}`"; + + internal static string IncompatibleTextureUVOriginUseTexture(TextureUVOriginSelection origin) => + $"UseTexture() of a resource with `{origin}` is not compatible with Unity's standard UV origin for texture reading {TextureUVOrigin.BottomLeft}. Are you trying to UseTexture() on a backbuffer?"; + // RenderGraphPass internal const string k_MoreThanOneResourceForMRTIndex = "You can only bind a single texture to a single index in a multiple render texture (MRT). Verify your indexes are correct."; @@ -141,7 +147,7 @@ internal static string UseTransientTextureInWrongPass(int transientIndex) => internal const string k_AttachmentsDoNotMatch = "Low level rendergraph error: Attachments in renderpass do not match."; - + internal const string k_MultisampledShaderResolveInvalidAttachmentSetup = "Low level rendergraph error: last subpass with shader resolve must have one color attachment."; @@ -165,6 +171,9 @@ internal static string UsingLegacyRenderGraph(string passName) => " You cannot use legacy passes with the Native Render Pass Compiler." + " The APIs that are compatible with the Native Render Pass Compiler are AddUnsafePass, AddComputePass and AddRasterRenderPass."; + internal static string IncompatibleTextureUVOriginStore(string firstAttachmentName, TextureUVOriginSelection firstAttachmentOrigin, string secondAttachmentName, TextureUVOriginSelection secondAttachmentOrigin) => + $"Texture attachment {firstAttachmentName} with uv origin {firstAttachmentOrigin} does not match with texture attachment {secondAttachmentName} with uv origin {secondAttachmentOrigin}. Storing both would result in contents being flipped."; + internal static string GetExceptionMessage(RenderGraphState state) { string caller = GetHigherCaller(); diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.cs index 1eef6660796..9b5ba940419 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.cs @@ -93,6 +93,17 @@ internal enum RenderGraphState Active = RecordingGraph | RecordingPass | Executing } + /// + /// The strategy that the render pipeline should use to determine the UV origin of RenderTextures who have an Unknown TextureUVOrigin when rendering. + /// + public enum RenderTextureUVOriginStrategy + { + /// RenderTextures are always treated as bottom left orientation. + BottomLeft, + /// RenderTextures may inherit the backbuffer attachment orientation if they are only used via attachment reads. + PropagateAttachmentOrientation + } + /// /// An object representing the internal context of a rendergraph pass execution. /// This object is public for technical reasons only and should not be used. @@ -105,17 +116,21 @@ public class InternalRenderGraphContext internal RenderGraphObjectPool renderGraphPool; internal RenderGraphDefaultResources defaultResources; internal RenderGraphPass executingPass; + internal NativeRenderPassCompiler.CompilerContextData compilerContext; internal bool contextlessTesting; } - // This whole thing is a bit of a mess InternalRenderGraphContext is public (but all members are internal) - // just because the C# standard says that all interface member function implementations should be public. + // InternalRenderGraphContext is public (but all members are internal) + // only because the C# standard says that all interface member function implementations must be public. // So below in for example the RasterGraphContext we can't implement the (internal) interface as // internal void FromInternalContext(InternalRenderGraphContext context) { ... } // So we have to make FromInternalContext public so InternalRenderGraphContext also becomes public. // This seems an oversight in c# where Interfaces used as Generic constraints could very well be useful // with internal only functions. + /// + /// Interface implemented by the different render graph contexts provided at execution timeline (via SetRenderFunc()) + /// internal interface IDerivedRendergraphContext { /// @@ -123,6 +138,16 @@ internal interface IDerivedRendergraphContext /// /// The context to convert public void FromInternalContext(InternalRenderGraphContext context); + + /// + /// Retrieves the TextureUVOrigin of the Render Graph texture from its handle. + /// + /// + /// This function can only be called when using the Native Render Pass Compiler (enabled by default). + /// + /// The texture handle to query. + /// The TextureUVOrigin of the texture. + public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle); } /// @@ -142,6 +167,9 @@ public void FromInternalContext(InternalRenderGraphContext context) wrappedContext = context; } + /// + public readonly TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) { return TextureUVOrigin.BottomLeft; } + ///Scriptable Render Context used for rendering. public ScriptableRenderContext renderContext { get => wrappedContext.renderContext; } ///Command Buffer used for rendering. @@ -171,6 +199,7 @@ public struct RasterGraphContext : IDerivedRendergraphContext public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } static internal RasterCommandBuffer rastercmd = new RasterCommandBuffer(null, null, false); + /// public void FromInternalContext(InternalRenderGraphContext context) { @@ -179,6 +208,19 @@ public void FromInternalContext(InternalRenderGraphContext context) rastercmd.m_ExecutingPass = context.executingPass; cmd = rastercmd; } + + /// + public readonly TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) + { + if (!SystemInfo.graphicsUVStartsAtTop) + return TextureUVOrigin.BottomLeft; + + if (wrappedContext.compilerContext != null) + { + return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); + } + return TextureUVOrigin.BottomLeft; + } } /// @@ -209,6 +251,19 @@ public void FromInternalContext(InternalRenderGraphContext context) computecmd.m_ExecutingPass = context.executingPass; cmd = computecmd; } + + /// + public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) + { + if (!SystemInfo.graphicsUVStartsAtTop) + return TextureUVOrigin.BottomLeft; + + if (wrappedContext.compilerContext != null) + { + return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); + } + return TextureUVOrigin.BottomLeft; + } } /// @@ -238,6 +293,19 @@ public void FromInternalContext(InternalRenderGraphContext context) unsCmd.m_ExecutingPass = context.executingPass; cmd = unsCmd; } + + /// + public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) + { + if (!SystemInfo.graphicsUVStartsAtTop) + return TextureUVOrigin.BottomLeft; + + if (wrappedContext.compilerContext != null) + { + return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); + } + return TextureUVOrigin.BottomLeft; + } } /// @@ -265,6 +333,8 @@ public struct RenderGraphParameters ///This allows you to run tests that rely on code execution the way to the pass render functions ///This also changes some behaviours with exception handling and error logging so the test framework can act on exceptions to validate behaviour better. internal bool invalidContextForTesting; + ///The strategy that the rendergraph should use to determine the texture uv origin if Unknown of RenderTextures when rendering. + public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy; } /// @@ -510,6 +580,7 @@ public void InitializeCompilationData(List passes, RenderGraphR CompiledGraph m_DefaultCompiledGraph = new(); CompiledGraph m_CurrentCompiledGraph; RenderGraphState m_RenderGraphState; + RenderTextureUVOriginStrategy m_renderTextureUVOriginStrategy; // Global container of registered render graphs, associated with the list of executions that have been registered for them. // When a RenderGraph is created, an entry is added to this dictionary. When that RenderGraph renders something, @@ -527,6 +598,15 @@ internal RenderGraphState RenderGraphState set { m_RenderGraphState = value; } } + + /// The strategy the Render Graph will take for the uv origin of RenderTextures in the graph. + public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy + { + get { return m_renderTextureUVOriginStrategy; } + internal set { m_renderTextureUVOriginStrategy = value; } + } + + /// If true, the Render Graph Viewer is active. public static bool isRenderGraphViewerActive => RenderGraphDebugSession.hasActiveDebugSession; @@ -1137,7 +1217,7 @@ public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer, bool forceReleas public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer) { CheckNotUsedWhenExecuting(); - + return m_Resources.ImportBuffer(graphicsBuffer); } @@ -1237,6 +1317,15 @@ void CheckNotUsingNativeRenderPassCompiler() } } + [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] + void CheckUsingNativeRenderPassCompiler() + { + if (enableValidityChecks && (!nativeRenderPassesEnabled || nativeCompiler == null)) + { + throw new InvalidOperationException("Only compatible with the Native Render Pass Compiler."); + } + } + [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] void CheckNotUsedWhenActive() { @@ -1244,6 +1333,14 @@ void CheckNotUsedWhenActive() throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.Active)); } + [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] + void CheckNotUsedWhenIdle() + { + if (enableValidityChecks && m_RenderGraphState == RenderGraphState.Idle) + throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.Active)); + } + + /// /// Add a new Raster Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute. /// @@ -1514,6 +1611,8 @@ public void BeginRecording(in RenderGraphParameters parameters) // Cannot do renderer list culling with compilation caching because it happens after compilation is done so it can lead to discrepancies. m_RendererListCulling = parameters.rendererListCulling && !m_EnableCompilationCaching; + m_renderTextureUVOriginStrategy = parameters.renderTextureUVOriginStrategy; + m_Resources.BeginRenderGraph(m_ExecutionCount++); if (m_DebugParameters.enableLogging) @@ -1635,6 +1734,9 @@ internal void Execute() else CompileRenderGraph(graphHash); + // Must be set after compilation when the compiler has been initialized + m_RenderGraphContext.compilerContext = nativeRenderPassesEnabled ? nativeCompiler?.contextData : null; + m_Resources.BeginExecute(m_CurrentFrameIndex); #if UNITY_EDITOR || DEVELOPMENT_BUILD @@ -1741,6 +1843,7 @@ void InvalidateContext() m_RenderGraphContext.cmd = null; m_RenderGraphContext.renderGraphPool = null; m_RenderGraphContext.defaultResources = null; + m_RenderGraphContext.compilerContext = null; } internal void OnPassAdded(RenderGraphPass pass) @@ -2175,7 +2278,7 @@ void UpdateResourceAllocationAndSynchronization() CompiledResourceInfo resourceInfo = resourceInfos[i]; bool sharedResource = m_Resources.IsRenderGraphResourceShared((RenderGraphResourceType)type, i); - + // Imported resource needs neither creation nor release. if (resourceInfo.imported && !sharedResource) continue; diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphBuilders.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphBuilders.cs index 7ea606fdc74..de45306a944 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphBuilders.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphBuilders.cs @@ -292,10 +292,30 @@ private void CheckNotUseFragment(TextureHandle tex) } } + [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] + private void CheckTextureUVOriginIsValid(ResourceHandle handle, TextureResource texRes) + { + if (texRes.textureUVOrigin == TextureUVOriginSelection.TopLeft) + { + var name = m_Resources.GetRenderGraphResourceName(handle); + throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type `{handle.type}` at index `{handle.index}` - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginUseTexture(texRes.textureUVOrigin)); + } + } + public void UseTexture(in TextureHandle input, AccessFlags flags) { CheckNotUseFragment(input); UseResource(input.handle, flags); + + if ((flags & AccessFlags.Read) == AccessFlags.Read) + { + if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) + { + TextureResource texRes = m_Resources.GetTextureResource(input.handle); + CheckTextureUVOriginIsValid(input.handle, texRes); + texRes.textureUVOrigin = TextureUVOriginSelection.BottomLeft; + } + } } public void UseGlobalTexture(int propertyId, AccessFlags flags) @@ -382,6 +402,53 @@ private void CheckUseFragment(TextureHandle tex, bool isDepth) throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnDepthTexture); } } + + if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) + { + var texResource = m_Resources.GetTextureResource(tex.handle); + TextureResource checkTexResource = null; + for (int i = 0; i < m_RenderPass.fragmentInputMaxIndex + 1; ++i) + { + if (m_RenderPass.fragmentInputAccess[i].textureHandle.IsValid()) + { + ref readonly TextureHandle texCheck = ref m_RenderPass.fragmentInputAccess[i].textureHandle; + checkTexResource = m_Resources.GetTextureResource(texCheck.handle); + if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) + { + var name = m_Resources.GetRenderGraphResourceName(tex.handle); + var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); + throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "input", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); + } + } + } + + for (int i = 0; i < m_RenderPass.colorBufferMaxIndex + 1; ++i) + { + if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid()) + { + ref readonly TextureHandle texCheck = ref m_RenderPass.colorBufferAccess[i].textureHandle; + checkTexResource = m_Resources.GetTextureResource(texCheck.handle); + if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) + { + var name = m_Resources.GetRenderGraphResourceName(tex.handle); + var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); + throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "render", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); + } + } + } + + if (!isDepth && m_RenderPass.depthAccess.textureHandle.IsValid()) + { + TextureHandle texCheck = m_RenderPass.depthAccess.textureHandle; + checkTexResource = m_Resources.GetTextureResource(texCheck.handle); + if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) + { + var name = m_Resources.GetRenderGraphResourceName(tex.handle); + var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); + throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "depth", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); + } + } + } } foreach (var globalTex in m_RenderPass.setGlobalsList) diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceRegistry.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceRegistry.cs index 72d83808841..5384103c02f 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceRegistry.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceRegistry.cs @@ -48,7 +48,7 @@ public struct RenderTargetInfo } /// - /// A helper struct describing the clear behavior of imported textures. + /// A helper struct describing the behavior of imported textures. /// public struct ImportResourceParams { @@ -66,6 +66,11 @@ public struct ImportResourceParams /// Fully discarding both multisampled and resolved data is not currently possible. /// public bool discardOnLastUse; + + /// + /// The uv orientation that should be used by texture resources imported into the rendergraph. + /// + public TextureUVOrigin textureUVOrigin; } class RenderGraphResourceRegistry @@ -456,6 +461,7 @@ internal TextureHandle ImportTexture(in RTHandle rt, bool isBuiltin = false) ImportResourceParams importParams = new ImportResourceParams(); importParams.clearOnFirstUse = false; importParams.discardOnLastUse = false; + importParams.textureUVOrigin = TextureUVOrigin.BottomLeft; return ImportTexture(rt, importParams, isBuiltin); } @@ -503,6 +509,7 @@ internal TextureHandle ImportTexture(in RTHandle rt, in ImportResourceParams imp texResource.desc.clearBuffer = importParams.clearOnFirstUse; texResource.desc.clearColor = importParams.clearColor; texResource.desc.discardBuffer = importParams.discardOnLastUse; + texResource.textureUVOrigin = (TextureUVOriginSelection)importParams.textureUVOrigin; var texHandle = new TextureHandle(newHandle, false, isBuiltin); @@ -542,6 +549,7 @@ internal TextureHandle ImportTexture(in RTHandle rt, RenderTargetInfo info, in I texResource.desc.clearBuffer = importParams.clearOnFirstUse; texResource.desc.clearColor = importParams.clearColor; texResource.desc.discardBuffer = importParams.discardOnLastUse; + texResource.textureUVOrigin = (TextureUVOriginSelection)importParams.textureUVOrigin; texResource.validDesc = false; // The desc above just contains enough info to make RenderTargetInfo not a full descriptor. // This means GetRenderTargetInfo will work for the handle but GetTextureResourceDesc will throw } @@ -657,6 +665,7 @@ internal TextureHandle ImportBackbuffer(RenderTargetIdentifier rt, in RenderTarg texResource.desc.clearBuffer = importParams.clearOnFirstUse; texResource.desc.clearColor = importParams.clearColor; texResource.desc.discardBuffer = importParams.discardOnLastUse; + texResource.textureUVOrigin = (TextureUVOriginSelection)importParams.textureUVOrigin; texResource.validDesc = false;// The desc above just contains enough info to make RenderTargetInfo not a full descriptor. // This means GetRenderTargetInfo will work for the handle but GetTextureResourceDesc will throw @@ -804,6 +813,7 @@ internal TextureHandle CreateTexture(in TextureDesc desc, int transientPassIndex texResource.validDesc = true; texResource.transientPassIndex = transientPassIndex; texResource.requestFallBack = desc.fallBackToBlackTexture; + texResource.textureUVOrigin = TextureUVOriginSelection.Unknown; return new TextureHandle(newHandle); } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceTexture.cs b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceTexture.cs index dd6dd499e19..eca99c43982 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceTexture.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphResourceTexture.cs @@ -21,7 +21,7 @@ public TextureAccess(TextureHandle handle, AccessFlags flags, int mipLevel, int this.mipLevel = mipLevel; this.depthSlice = depthSlice; } - + public TextureAccess(TextureAccess access, TextureHandle handle) { this.textureHandle = handle; @@ -31,6 +31,48 @@ public TextureAccess(TextureAccess access, TextureHandle handle) } } + /// + /// Represents the origin of UV coordinates for a texture. It represents how Unity stores the content, + /// independent of the active graphics API. The UV coordinate (0,0) in the shader will either sample + /// the bottom left pixel of the image, or the top left pixel (flipped). + /// + public enum TextureUVOrigin + { + /// + /// The UV coordinate (0,0) in a shader will sample the BOTTOM left texel of the texture. This matched the OpenGL standard, which is also the Unity standard for textures. + /// To ensure this behavior, Unity will store the content for texture upside down (flipped) on modern graphics APIs. + /// + BottomLeft, + /// + /// The UV coordinate (0,0) in a shader will sample the TOP left texel of the texture. This matches the standard of modern graphics APIs (Vulkan, DX, Metal,...). + /// The actual backbuffer will have a TopLeft orientation when a modern graphics API is active. + /// + TopLeft + } + + /// + /// Represents the origin of UV coordinates for a texture. It represents how Unity stores the content, + /// independent of the active graphics API. The UV coordinate (0,0) in the shader will either sample + /// the bottom left pixel of the image, or the top left pixel (flipped). + /// + internal enum TextureUVOriginSelection + { + /// + /// The UV coordinate (0,0) in a shader will sample the BOTTOM left texel of the texture. This matched the OpenGL standard, which is also the Unity standard for textures. + /// To ensure this behavior, Unity will store the content for texture upside down (flipped) on modern graphics APIs. + /// + BottomLeft, + /// + /// The UV coordinate (0,0) in a shader will sample the TOP left texel of the texture. This matches the standard of modern graphics APIs (Vulkan, DX, Metal,...). + /// The actual backbuffer will have a TopLeft orientation when a modern graphics API is active. + /// + TopLeft, + /// + /// The orientation has not been assigned yet. + /// + Unknown + } + /// /// An abstract handle representing a texture resource as known by one particular record + execute of the render graph. @@ -481,6 +523,8 @@ class TextureResource : RenderGraphResource { static int m_TextureCreationIndex; + internal TextureUVOriginSelection textureUVOrigin; + public override string GetName() { if (imported && !shared) diff --git a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/ComputeRayTracingAccelStruct.cs b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/ComputeRayTracingAccelStruct.cs index 1680fd1588f..28b7cc3e1cb 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/ComputeRayTracingAccelStruct.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/ComputeRayTracingAccelStruct.cs @@ -508,12 +508,15 @@ TopLevelAccelStruct CpuBuildForTopLevelAccelStruct(CommandBuffer cmd, RadeonRays var m = ConvertTranform(instance.localToWorldTransform); var bounds = GeometryUtility.CalculateBounds(new Vector3[] - { new Vector3(aabb.Min.x, aabb.Min.y, aabb.Max.z), - new Vector3(aabb.Min.x, aabb.Max.y, aabb.Min.z), - new Vector3(aabb.Min.x, aabb.Max.y, aabb.Max.z), - new Vector3(aabb.Max.x, aabb.Min.y, aabb.Max.z), - new Vector3(aabb.Max.x, aabb.Max.y, aabb.Min.z), - new Vector3(aabb.Max.x, aabb.Max.y, aabb.Max.z) + { + new Vector3(aabb.Min.x, aabb.Min.y, aabb.Min.z), + new Vector3(aabb.Min.x, aabb.Min.y, aabb.Max.z), + new Vector3(aabb.Min.x, aabb.Max.y, aabb.Min.z), + new Vector3(aabb.Min.x, aabb.Max.y, aabb.Max.z), + new Vector3(aabb.Max.x, aabb.Min.y, aabb.Min.z), + new Vector3(aabb.Max.x, aabb.Min.y, aabb.Max.z), + new Vector3(aabb.Max.x, aabb.Max.y, aabb.Min.z), + new Vector3(aabb.Max.x, aabb.Max.y, aabb.Max.z) }, m); prims[i].primID = (uint)i; diff --git a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/RadeonRaysAPI.cs b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/RadeonRaysAPI.cs index 96364bdb9b9..dd6014c93b0 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/RadeonRaysAPI.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/RadeonRaysAPI.cs @@ -119,18 +119,18 @@ public static Transform TRS(float3 translation, float3 rotation, float3 scale) public Transform Inverse() { - float4x4 m = new float4x4(); - m[0] = new float4(row0.x, row1.x, row2.x, 0); - m[1] = new float4(row0.y, row1.y, row2.y, 0); - m[2] = new float4(row0.z, row1.z, row2.z, 0); - m[3] = new float4(row0.w, row1.w, row2.w, 1); + float3x3 m = new float3x3(); + m[0] = new float3(row0.x, row1.x, row2.x); + m[1] = new float3(row0.y, row1.y, row2.y); + m[2] = new float3(row0.z, row1.z, row2.z); - m = math.fastinverse(m); + m = math.inverse(m); + var t = -math.mul(m, new float3(row0.w, row1.w, row2.w)); Transform res; - res.row0 = new float4(m[0].x, m[1].x, m[2].x, m[3].x); - res.row1 = new float4(m[0].y, m[1].y, m[2].y, m[3].y); - res.row2 = new float4(m[0].z, m[1].z, m[2].z, m[3].z); + res.row0 = new float4(m[0].x, m[1].x, m[2].x, t.x); + res.row1 = new float4(m[0].y, m[1].y, m[2].y, t.y); + res.row2 = new float4(m[0].z, m[1].z, m[2].z, t.z); return res; } diff --git a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/kernels/transform.hlsl b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/kernels/transform.hlsl index 4ed30014fa7..cdd4df9f2f6 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/kernels/transform.hlsl +++ b/Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Compute/RadeonRays/kernels/transform.hlsl @@ -66,14 +66,14 @@ Transform Inverse(in Transform t) Aabb TransformAabb(in Aabb aabb, in Transform t) { - float3 p0 = aabb.pmin; + float3 p0 = float3(aabb.pmin.x, aabb.pmin.y, aabb.pmin.z); float3 p1 = float3(aabb.pmin.x, aabb.pmin.y, aabb.pmax.z); float3 p2 = float3(aabb.pmin.x, aabb.pmax.y, aabb.pmin.z); float3 p3 = float3(aabb.pmin.x, aabb.pmax.y, aabb.pmax.z); - float3 p4 = float3(aabb.pmax.x, aabb.pmin.y, aabb.pmax.z); - float3 p5 = float3(aabb.pmax.x, aabb.pmax.y, aabb.pmin.z); - float3 p6 = float3(aabb.pmax.x, aabb.pmax.y, aabb.pmax.z); - float3 p7 = aabb.pmax; + float3 p4 = float3(aabb.pmax.x, aabb.pmin.y, aabb.pmin.z); + float3 p5 = float3(aabb.pmax.x, aabb.pmin.y, aabb.pmax.z); + float3 p6 = float3(aabb.pmax.x, aabb.pmax.y, aabb.pmin.z); + float3 p7 = float3(aabb.pmax.x, aabb.pmax.y, aabb.pmax.z); p0 = TransformPointT(p0, t); p1 = TransformPointT(p1, t); @@ -98,4 +98,4 @@ Aabb TransformAabb(in Aabb aabb, in Transform t) } #endif -#endif // TRANSFORM_HLSL \ No newline at end of file +#endif // TRANSFORM_HLSL diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef b/Packages/com.unity.render-pipelines.core/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef index 3052597a3f0..d633fcd01c8 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef +++ b/Packages/com.unity.render-pipelines.core/Runtime/Unity.RenderPipelines.Core.Runtime.asmdef @@ -49,6 +49,11 @@ "name": "com.unity.modules.amd", "expression": "1.0.0", "define": "ENABLE_AMD_MODULE" + }, + { + "name": "com.unity.modules.physics", + "expression": "1.0.0", + "define": "ENABLE_PHYSICS_MODULE" } ], "noEngineReferences": false diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/DLSSOptions.cs b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/DLSSOptions.cs index 41258437521..e67f84b8853 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/DLSSOptions.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/DLSSOptions.cs @@ -17,25 +17,22 @@ [Serializable] public class DLSSOptions : UpscalerOptions { - [EnumOption(typeof(DLSSQuality), "DLSS Quality Mode")] - public int DLSSQualityMode = (int)DLSSQuality.MaximumQuality; + [Tooltip("Selects a performance quality setting for NVIDIA Deep Learning Super Sampling (DLSS).")] + public DLSSQuality DLSSQualityMode = DLSSQuality.MaximumQuality; - [BoolOption("Force Quality Mode")] + [Tooltip("Forces a fixed resolution scale derived from the selected quality mode, ignoring dynamic resolution.")] public bool FixedResolutionMode = false; - // TODO: fix available preset values, currently all values are displayed. - // every preset have their own list of available presets, that may differ from each other. - // need a way to represent the available subset of enum values for each preset to enforce the value requirements. - [EnumOption(typeof(DLSSPreset), "DLSS Preset for Quality")] - public int DLSSRenderPresetQuality; - [EnumOption(typeof(DLSSPreset), "DLSS Preset for Balanced")] - public int DLSSRenderPresetBalanced; - [EnumOption(typeof(DLSSPreset), "DLSS Preset for Performance")] - public int DLSSRenderPresetPerformance; - [EnumOption(typeof(DLSSPreset), "DLSS Preset for Ultra Performance")] - public int DLSSRenderPresetUltraPerformance; - [EnumOption(typeof(DLSSPreset), "DLSS Preset for DLAA")] - public int DLSSRenderPresetDLAA; + [Tooltip("DLSS will use the specified render preset for the Quality mode.")] + public DLSSPreset DLSSRenderPresetQuality; + [Tooltip("DLSS will use the specified render preset for the Balanced mode.")] + public DLSSPreset DLSSRenderPresetBalanced; + [Tooltip("DLSS will use the specified render preset for the Performance mode.")] + public DLSSPreset DLSSRenderPresetPerformance; + [Tooltip("DLSS will use the specified render preset for the Ultra Performance mode.")] + public DLSSPreset DLSSRenderPresetUltraPerformance; + [Tooltip("DLSS will use the specified render preset for the DLAA mode.")] + public DLSSPreset DLSSRenderPresetDLAA; } #endif // ENABLE_UPSCALER_FRAMEWORK && ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/FSR2Options.cs b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/FSR2Options.cs index e3f4bb4d95b..24fd49124a5 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/FSR2Options.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/FSR2Options.cs @@ -17,16 +17,17 @@ [Serializable] public class FSR2Options : UpscalerOptions { - [EnumOption(typeof(FSR2Quality), "FSR2 Quality Mode")] - public int FSR2QualityMode = (int)FSR2Quality.Quality; + [Tooltip("Selects a performance quality setting for AMD FidelityFX 2.0 Super Resolution (FSR2).")] + public FSR2Quality FSR2QualityMode = FSR2Quality.Quality; - [BoolOption("Fixed Resolution")] + [Tooltip("Forces a fixed resolution scale derived from the selected quality mode, ignoring dynamic resolution.")] public bool FixedResolutionMode = false; - [BoolOption("Enable Sharpening")] + [Tooltip("Enable an additional sharpening pass on FidelityFX 2.0 Super Resolution (FSR2).")] public bool EnableSharpening = false; - [FloatOption(0.0f, 1.0f, "Sharpness")] + [Tooltip("The sharpness value between 0 and 1, where 0 is no additional sharpness and 1 is maximum additional sharpness.")] + [Range(0.0f, 1.0f)] public float Sharpness = 0.92f; }; diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/IUpscalerOptions.cs b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/IUpscalerOptions.cs index 1553d21d07e..37b4f310241 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/IUpscalerOptions.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Upscaling/IUpscalerOptions.cs @@ -9,380 +9,6 @@ namespace UnityEngine.Rendering { #nullable enable - #region Attributes - - /// - /// Base class for all custom option attributes. - /// UpscalerOptions attributes facilitate GUI rendering for a given option by sepcifying its type, range of values and display label. - /// - public abstract class BaseOptionAttribute : PropertyAttribute // Using PropertyAttribute for Unity Editor integration - { - public string? DisplayName { get; protected set; } - - protected BaseOptionAttribute(string? displayName = null) - { - DisplayName = displayName; - } - } - - - /// - /// Marks an int field as representing an enum value. - /// - [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] - public class EnumOptionAttribute : BaseOptionAttribute - { - public Type EnumType { get; private set; } - - public EnumOptionAttribute(Type enumType, string? displayName = null) : base(displayName) - { - if (enumType == null || !enumType.IsEnum) - { - throw new ArgumentException("EnumType must be a valid Enum type.", nameof(enumType)); - } - EnumType = enumType; - } - } - - /// - /// Marks a float field as an option. - /// - [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] - public class FloatOptionAttribute : BaseOptionAttribute - { - public float Min { get; private set; } - public float Max { get; private set; } - public bool HasRange { get; private set; } // Indicates if min/max were explicitly set - public FloatOptionAttribute(string? displayName = null) : base(displayName) - { - HasRange = false; - Min = 0f; // Default, will be ignored if HasRange is false - Max = 0f; - } - public FloatOptionAttribute(float min, float max, string? displayName = null) : base(displayName) - { - HasRange = true; - Min = min; - Max = max; - } - } - - /// - /// Marks an int field as an option. - /// - [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] - public class IntOptionAttribute : BaseOptionAttribute - { - public int Min { get; private set; } - public int Max { get; private set; } - public bool HasRange { get; private set; } // Indicates if min/max were explicitly set - - // Constructor for int without range - public IntOptionAttribute(string? displayName = null) : base(displayName) - { - HasRange = false; - Min = 0; // Default - Max = 0; - } - - // Constructor for int with range - public IntOptionAttribute(int min, int max, string? displayName = null) : base(displayName) - { - HasRange = true; - Min = min; - Max = max; - } - } - - /// - /// Marks a boolean field as an option. - /// - [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] - public class BoolOptionAttribute : BaseOptionAttribute - { - public BoolOptionAttribute(string? displayName = null) : base(displayName) { } - } - #endregion - - #region OptionsBase - /// - /// Represents a single configurable option that can be read from and written to. - /// - public interface IOption - { - string Id { get; } // A unique identifier for the option (e.g., field name) - string DisplayName { get; } // A user-friendly name for the option - Type ValueType { get; } // The actual C# type of the option's value (e.g., typeof(float), typeof(DLSSQuality)) - - abstract object? GetValue(object targetInstance); - abstract void SetValue(object targetInstance, object? newValue); - } - - /// - /// Represents an option whose value is an enum. - /// - public interface IEnumOption : IOption - { - Type EnumType { get; } // The specific enum Type (e.g., typeof(DLSSQuality)) - Array GetEnumValues(); // Returns an array of the actual enum values - string[] GetEnumNames(); // Returns an array of the enum names (for display) - } - - /// - /// Represents a numeric option that has a defined minimum and maximum value. - /// - public interface IRangeOption : IOption - { - float MinValue { get; } // Using float for generics, int ranges will cast - float MaxValue { get; } // Using float for generics, int ranges will cast - bool HasRange { get; } // True if a min/max was explicitly set - } - - - /// - /// Abstract base class for option implementations, handles common properties. - /// - internal abstract class OptionBase : IOption - { - protected readonly FieldInfo _fieldInfo; - - public string Id { get; private set; } - public string DisplayName { get; private set; } - public abstract Type ValueType { get; } - - protected OptionBase(FieldInfo fieldInfo, string displayName) - { - _fieldInfo = fieldInfo ?? throw new ArgumentNullException(nameof(fieldInfo)); - Id = fieldInfo.Name; - DisplayName = displayName; - } - - public virtual object? GetValue(object targetInstance) - { - if (targetInstance == null || !_fieldInfo.DeclaringType!.IsAssignableFrom(targetInstance.GetType())) - { - throw new ArgumentException($"Target instance is not compatible with option's field type. Expected {_fieldInfo.DeclaringType.Name}, got {targetInstance?.GetType().Name}.", nameof(targetInstance)); - } - return _fieldInfo.GetValue(targetInstance); - } - - public virtual void SetValue(object targetInstance, object? newValue) - { - if (targetInstance == null || !_fieldInfo.DeclaringType!.IsAssignableFrom(targetInstance.GetType())) - { - throw new ArgumentException($"Target instance is not compatible with option's field type. Expected {_fieldInfo.DeclaringType.Name}, got {targetInstance?.GetType().Name}.", nameof(targetInstance)); - } - _fieldInfo.SetValue(targetInstance, newValue); - } - } - #endregion - - - #region TypedOptions - /// - /// Represents an integer option backed by an enum. - /// - internal class EnumIntOption : OptionBase, IEnumOption - { - public Type EnumType { get; private set; } - - public override Type ValueType => EnumType; - - public EnumIntOption(FieldInfo fieldInfo, string displayName, Type enumType) - : base(fieldInfo, displayName) - { - if (!enumType.IsEnum) - { - throw new ArgumentException($"Provided type '{enumType.Name}' is not an enum.", nameof(enumType)); - } - if (fieldInfo.FieldType != typeof(int)) - { - throw new ArgumentException($"EnumIntOption can only wrap int fields. Field '{fieldInfo.Name}' is of type '{fieldInfo.FieldType.Name}'.", nameof(fieldInfo)); - } - EnumType = enumType; - } - - public override object? GetValue(object targetInstance) - { - int intValue = (int)(base.GetValue(targetInstance) ?? 0); // Handle potential null for value types - return Enum.ToObject(EnumType, intValue); - } - - public override void SetValue(object targetInstance, object? newValue) - { - if (newValue != null && newValue.GetType() != EnumType) - { - throw new ArgumentException($"Cannot set EnumIntOption '{DisplayName}': value type mismatch. Expected '{EnumType.Name}', got '{newValue?.GetType().Name}'.", nameof(newValue)); - } - base.SetValue(targetInstance, Convert.ToInt32(newValue)); - } - - public Array GetEnumValues() => Enum.GetValues(EnumType); - public string[] GetEnumNames() => Enum.GetNames(EnumType); - } - - /// - /// Represents a boolean option. - /// - internal class BoolOption : OptionBase - { - public override Type ValueType => typeof(bool); - - public BoolOption(FieldInfo fieldInfo, string displayName) - : base(fieldInfo, displayName) - { - if (fieldInfo.FieldType != typeof(bool)) - { - throw new ArgumentException($"BoolOption can only wrap bool fields. Field '{fieldInfo.Name}' is of type '{fieldInfo.FieldType.Name}'.", nameof(fieldInfo)); - } - } - } - - /// - /// Represents a float option, with an optional min/max range. - /// - internal class FloatOption : OptionBase, IRangeOption - { - public override Type ValueType => typeof(float); - public float MinValue { get; private set; } - public float MaxValue { get; private set; } - public bool HasRange { get; private set; } - - public FloatOption(FieldInfo fieldInfo, string displayName, float min, float max, bool hasRange) - : base(fieldInfo, displayName) - { - if (fieldInfo.FieldType != typeof(float)) - { - throw new ArgumentException($"FloatOption can only wrap float fields. Field '{fieldInfo.Name}' is of type '{fieldInfo.FieldType.Name}'.", nameof(fieldInfo)); - } - MinValue = min; - MaxValue = max; - HasRange = hasRange; - } - } - - /// - /// Represents an int option, with an optional min/max range. - /// - internal class IntOption : OptionBase, IRangeOption - { - public override Type ValueType => typeof(int); - public float MinValue { get; private set; } // Storing as float, will cast to int when used - public float MaxValue { get; private set; } // Storing as float, will cast to int when used - public bool HasRange { get; private set; } - - public IntOption(FieldInfo fieldInfo, string displayName, int min, int max, bool hasRange) - : base(fieldInfo, displayName) - { - if (fieldInfo.FieldType != typeof(int)) - { - throw new ArgumentException($"IntOption can only wrap int fields. Field '{fieldInfo.Name}' is of type '{fieldInfo.FieldType.Name}'.", nameof(fieldInfo)); - } - MinValue = min; - MaxValue = max; - HasRange = hasRange; - } - } - - #endregion - - #region OptionsRegistry - /// - /// Provides methods to generically inspect UpscalerOptions instances - /// and retrieve their configurable options. - /// - public static class UpscalerOptionsRegistry - { - // Cached reflection results - private static readonly Dictionary> s_CachedOptions = new(); - -#if UNITY_EDITOR - /// - /// Discovers and returns all configurable options for a given UpscalerOptions instance. - /// This method uses reflection and caches results for performance. - /// - /// The UpscalerOptions instance to inspect. - /// A list of IOption objects representing the configurable options. - [Preserve] // Prevent IL2CPP stripping if attributes/reflection are only used by this. - public static IReadOnlyList GetConfigurableOptions(UpscalerOptions optionsInstance) - { - if (optionsInstance == null) - { - Debug.LogWarning("GetConfigurableOptions called with a null options instance."); - return new List(); - } - - Type optionsType = optionsInstance.GetType(); - - // Use cached metadata if available - if (s_CachedOptions.TryGetValue(optionsType, out var optionsList)) - { - return optionsList; - } - - // Otherwise, perform reflection and build the metadata - List discoveredOptions = new List(); - - // Get all instance fields, both public and non-public (for [SerializeField] private fields) - FieldInfo[] fields = optionsType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - foreach (FieldInfo field in fields) - { - // Check for our custom option attributes - BaseOptionAttribute? attr = field.GetCustomAttribute(true); - - if (attr == null) continue; // Not an option we care about - - // Determine the display name, prioritizing the one from the attribute - string displayName = attr.DisplayName ?? ObjectNames.NicifyVariableName(field.Name); - - // Create the appropriate IOption wrapper based on the attribute type - if (attr is EnumOptionAttribute enumAttr) - { - discoveredOptions.Add(new EnumIntOption(field, displayName, enumAttr.EnumType)); - } - else if (attr is FloatOptionAttribute floatAttr) - { - discoveredOptions.Add(new FloatOption(field, displayName, floatAttr.Min, floatAttr.Max, floatAttr.HasRange)); - } - else if (attr is IntOptionAttribute intAttr) - { - discoveredOptions.Add(new IntOption(field, displayName, intAttr.Min, intAttr.Max, intAttr.HasRange)); - } - else if (attr is BoolOptionAttribute) - { - discoveredOptions.Add(new BoolOption(field, displayName)); - } - else - { - Debug.LogWarning($"Unhandled BaseOptionAttribute type for field '{field.Name}' in '{optionsType.Name}': {attr.GetType().Name}."); - } - } - - // Cache and return - s_CachedOptions[optionsType] = discoveredOptions; - return discoveredOptions; - } - - /// - /// Finds a specific option by its ID (field name). - /// - /// The UpscalerOptions instance. - /// The ID (field name) of the option to find. - /// The IOption object if found, otherwise null. - public static IOption? GetOptionById(UpscalerOptions optionsInstance, string optionId) - { - foreach(IOption opt in GetConfigurableOptions(optionsInstance)) - { - if (opt.Id == optionId) - return opt; - } - return null; - } -#endif - }; - #endregion - [Serializable] public class UpscalerOptions : ScriptableObject @@ -399,75 +25,13 @@ public UpsamplerScheduleType InjectionPoint set => m_InjectionPoint = value; } - [SerializeField] private string m_UpscalerName = ""; - [SerializeField] private UpsamplerScheduleType m_InjectionPoint = UpsamplerScheduleType.BeforePost; + [SerializeField, HideInInspector] + private string m_UpscalerName = ""; + [SerializeField, HideInInspector] // hide in inspector for URP, HDRP manually renders it + private UpsamplerScheduleType m_InjectionPoint = UpsamplerScheduleType.BeforePost; #if UNITY_EDITOR - public bool DrawOptionsEditorGUI() - { - bool optionUpdated = false; - IReadOnlyList options = UpscalerOptionsRegistry.GetConfigurableOptions(this); - foreach (IOption opt in options) - { - object? currentValue = opt.GetValue(this); - object? newValue = null; // store the value returned by the GUI control - - // ranged int/float options - if (opt is IRangeOption rangeOption && rangeOption.HasRange) - { - if (opt.ValueType == typeof(float)) - { - float currentFloatValue = (float)currentValue!; - newValue = EditorGUILayout.Slider(opt.DisplayName, currentFloatValue, rangeOption.MinValue, rangeOption.MaxValue); - } - else if (opt.ValueType == typeof(int)) - { - int currentIntValue = (int)currentValue!; - newValue = EditorGUILayout.IntSlider(opt.DisplayName, currentIntValue, (int)rangeOption.MinValue, (int)rangeOption.MaxValue); - } - else - { - // Fallback for unexpected IRangeOption types (shouldn't happen if attributes are correct) - EditorGUILayout.LabelField(opt.DisplayName, $"Range Type (Unhandled): {opt.ValueType.Name}, Value: {currentValue?.ToString() ?? "N/A"}"); - } - } - // regular options - else - { - if (opt is IEnumOption enumOption) - { - Enum currentEnumValue = (Enum)currentValue!; - newValue = EditorGUILayout.EnumPopup(enumOption.DisplayName, currentEnumValue); - } - else if (opt.ValueType == typeof(float)) - { - float currentFloatValue = (float)currentValue!; - newValue = EditorGUILayout.FloatField(opt.DisplayName, currentFloatValue); - } - else if (opt.ValueType == typeof(int)) - { - int currentIntValue = (int)currentValue!; - newValue = EditorGUILayout.IntField(opt.DisplayName, currentIntValue); - } - else if (opt.ValueType == typeof(bool)) - { - bool currentBoolValue = (bool)currentValue!; - newValue = EditorGUILayout.Toggle(opt.DisplayName, currentBoolValue); - } - } - - bool valueChanged = newValue != null && !newValue.Equals(currentValue); - if (valueChanged) - { - opt.SetValue(this, newValue); - optionUpdated = true; - } - } - - return optionUpdated; - } - // The core method to ensure options exist and are linked. // It operates on a SerializedProperty representing the list. public static bool ValidateSerializedUpscalerOptionReferencesWithinRPAsset(ScriptableObject parentRPAsset, SerializedProperty optionsListProp) diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs b/Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs index bfbc4484fd2..b91c8634e83 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs @@ -5,6 +5,11 @@ using UnityEngine.Experimental.Rendering; using System.Runtime.CompilerServices; +#if UNITY_EDITOR +using UnityEditor; +using System.Reflection; +#endif + namespace UnityEngine.Rendering { using static UnityEngine.Rendering.HableCurve; @@ -1862,6 +1867,19 @@ public static void EnsureFolderTreeInAssetFilePath(string filePath) } } } + + /// + /// Returns the icon for the given type if it has an IconAttribute. + /// + /// Type parameter + /// Valid icon texture, or null none is found + public static Texture2D GetIconForType() where T : UnityEngine.Object + { + var iconAttribute = typeof(T).GetCustomAttribute(); + if (iconAttribute == null || string.IsNullOrEmpty(iconAttribute.path)) + return null; + return EditorGUIUtility.IconContent(iconAttribute.path)?.image as Texture2D; + } #endif /// diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs b/Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs deleted file mode 100644 index 37a9949f8b3..00000000000 --- a/Packages/com.unity.render-pipelines.core/Runtime/Volume/IVolume.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; - -namespace UnityEngine.Rendering -{ - /// - /// Defines the basic structure for a Volume, providing the necessary properties for determining - /// whether the volume should be applied globally to the scene or to specific colliders. - /// - /// - /// This interface serves as a contract for systems that implement volume logic, enabling - /// reusable code for volume-based behaviors such as rendering effects, post-processing, or scene-specific logic. - /// The interface is commonly implemented by components that define volumes in a scene, - /// allowing for flexibility in determining how the volume interacts with the scene. A volume can either be global - /// (affecting the entire scene) or local (restricted to specific colliders). - /// This interface is also helpful for drawing gizmos in the scene view, as it allows for visual representation - /// of volumes in the editor based on their settings. - /// - public interface IVolume - { - /// - /// Gets or sets a value indicating whether the volume applies to the entire scene. - /// If true, the volume is global and affects all objects within the scene. - /// If false, the volume is local and only affects the objects within the specified colliders. - /// - /// - /// When set to true, the volume's effects will be applied universally across the scene, - /// without considering individual colliders. When false, the volume will interact only with - /// the objects inside the colliders defined in . - /// - bool isGlobal { get; set; } - - /// - /// A list of colliders that define the area of influence of the volume when is set to false. - /// - /// - /// This property holds the colliders that restrict the volume's effects to specific areas of the scene. - /// It is only relevant when is false, and defines the boundaries of where the volume is applied. - /// - List colliders { get; } - } - -} diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Volume/Volume.cs b/Packages/com.unity.render-pipelines.core/Runtime/Volume/Volume.cs index 88c36382a08..fe1d323c2e0 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Volume/Volume.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Volume/Volume.cs @@ -12,7 +12,7 @@ namespace UnityEngine.Rendering [ExecuteAlways] [AddComponentMenu("Miscellaneous/Volume")] [Icon("Packages/com.unity.render-pipelines.core/Editor/Icons/Processed/Volume Icon.asset")] - public class Volume : MonoBehaviour, IVolume + public class Volume : MonoBehaviour { [SerializeField, FormerlySerializedAs("isGlobal")] bool m_IsGlobal = true; @@ -99,13 +99,15 @@ public VolumeProfile profile set => m_InternalProfile = value; } +#if ENABLE_PHYSICS_MODULE readonly List m_Colliders = new List(); /// /// The colliders of the volume if is false /// public List colliders => m_Colliders; - +#endif + GameObject m_CachedGameObject; internal GameObject cachedGameObject => m_CachedGameObject; @@ -157,7 +159,9 @@ void Update() /// public void UpdateColliders() { +#if ENABLE_PHYSICS_MODULE GetComponents(m_Colliders); +#endif } internal void UpdateLayer() diff --git a/Packages/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs b/Packages/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs index 1278f15c9c4..6065a5698f6 100644 --- a/Packages/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs +++ b/Packages/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs @@ -749,6 +749,10 @@ public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask) beginVolumeStackUpdate?.Invoke(stack, camera); #endif +#if ENABLE_PHYSICS_MODULE + bool physicsBackendIsNone = Physics.GetCurrentIntegrationInfo().isFallback; +#endif + // Traverse all volumes int numVolumes = volumes.Count; for (int i = 0; i < numVolumes; i++) @@ -777,6 +781,11 @@ public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask) if (onlyGlobal) continue; + // NOTE: Local volumes require physics module to be enabled +#if ENABLE_PHYSICS_MODULE + if (physicsBackendIsNone) + continue; + // If volume isn't global and has no collider, skip it as it's useless var colliders = volume.colliders; int numColliders = colliders.Count; @@ -815,6 +824,7 @@ public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask) // No need to clamp01 the interpolation factor or weight as both are always in [0;1[ range OverrideData(stack, volume, interpFactor * Mathf.Clamp01(volume.weight)); +#endif } #if UNITY_EDITOR || DEVELOPMENT_BUILD diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/NativePassCompilerRenderGraphTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/NativePassCompilerRenderGraphTests.cs index 20d922628a0..eba3bffccdf 100644 --- a/Packages/com.unity.render-pipelines.core/Tests/Editor/NativePassCompilerRenderGraphTests.cs +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/NativePassCompilerRenderGraphTests.cs @@ -27,22 +27,31 @@ TextureDesc SimpleTextureDesc(string name, int w, int h, int samples) return result; } - class TestBuffers + class TestRenderTargets { public TextureHandle backBuffer; public TextureHandle depthBuffer; - public TextureHandle[] extraBuffers = new TextureHandle[10]; + public TextureHandle[] extraTextures = new TextureHandle[10]; public TextureHandle extraDepthBuffer; + public TextureHandle extraDepthBufferBottomLeft; + public TextureHandle extraTextureBottomLeft; + public TextureHandle extraTextureTopLeft; }; - TestBuffers ImportAndCreateBuffers(RenderGraph g) + TestRenderTargets ImportAndCreateRenderTargets(RenderGraph g) { - TestBuffers result = new TestBuffers(); + TestRenderTargets result = new TestRenderTargets(); var backBuffer = BuiltinRenderTextureType.CameraTarget; var backBufferHandle = RTHandles.Alloc(backBuffer, "Backbuffer Color"); var depthBuffer = BuiltinRenderTextureType.Depth; var depthBufferHandle = RTHandles.Alloc(depthBuffer, "Backbuffer Depth"); var extraDepthBufferHandle = RTHandles.Alloc(depthBuffer, "Extra Depth Buffer"); + var extraDepthBufferBottomLeftHandle = RTHandles.Alloc(depthBuffer, "Extra Depth Buffer Bottom Left"); + var extraTextureTopLeftHandle = RTHandles.Alloc(backBuffer, "ExtraTextureTopLeft"); + var extraTextureBottomLeftHandle = RTHandles.Alloc(backBuffer,"ExtraTextureBottomLeft"); + + ImportResourceParams importParams = new ImportResourceParams(); + importParams.textureUVOrigin = TextureUVOrigin.TopLeft; RenderTargetInfo importInfo = new RenderTargetInfo(); RenderTargetInfo importInfoDepth = new RenderTargetInfo(); @@ -51,20 +60,28 @@ TestBuffers ImportAndCreateBuffers(RenderGraph g) importInfo.volumeDepth = 1; importInfo.msaaSamples = 1; importInfo.format = GraphicsFormat.R16G16B16A16_SFloat; - result.backBuffer = g.ImportTexture(backBufferHandle, importInfo); + result.backBuffer = g.ImportTexture(backBufferHandle, importInfo, importParams); importInfoDepth = importInfo; importInfoDepth.format = GraphicsFormat.D32_SFloat_S8_UInt; - result.depthBuffer = g.ImportTexture(depthBufferHandle, importInfoDepth); + result.depthBuffer = g.ImportTexture(depthBufferHandle, importInfoDepth, importParams); + + importInfoDepth.format = GraphicsFormat.D24_UNorm; + result.extraDepthBuffer = g.ImportTexture(extraDepthBufferHandle, importInfoDepth, importParams); - importInfo.format = GraphicsFormat.D24_UNorm; - result.extraDepthBuffer = g.ImportTexture(extraDepthBufferHandle, importInfoDepth); + importParams.textureUVOrigin = TextureUVOrigin.BottomLeft; + result.extraDepthBufferBottomLeft = g.ImportTexture(extraDepthBufferBottomLeftHandle, importInfoDepth, importParams); - for (int i = 0; i < result.extraBuffers.Length; i++) + for (int i = 0; i < result.extraTextures.Length; i++) { - result.extraBuffers[i] = g.CreateTexture(SimpleTextureDesc("ExtraBuffer" + i, 1024, 768, 1)); + result.extraTextures[i] = g.CreateTexture(SimpleTextureDesc("ExtraTexture" + i, 1024, 768, 1)); } + importParams.textureUVOrigin = TextureUVOrigin.TopLeft; + result.extraTextureTopLeft = g.ImportTexture(extraTextureTopLeftHandle, importInfo, importParams); + importParams.textureUVOrigin = TextureUVOrigin.BottomLeft; + result.extraTextureBottomLeft = g.ImportTexture(extraTextureBottomLeftHandle, importInfo, importParams); + return result; } @@ -91,31 +108,31 @@ public void SimpleMergePasses() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to 0,1 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render extra bits to 1 using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 1, AccessFlags.Write); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -144,14 +161,14 @@ public void MergeNonRenderPasses() { RenderGraph g = new RenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to 0,1 { var builder = g.AddRasterRenderPass("TestPass0", out var passData); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.Dispose(); } @@ -167,10 +184,10 @@ public void MergeNonRenderPasses() // Render to final buffer { var builder = g.AddRasterRenderPass("TestPass2", out var passData); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 1, AccessFlags.Write); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.Dispose(); } @@ -188,21 +205,21 @@ public void MergeNonRenderPasses() public void MergeDepthPassWithNoDepthPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // with no depth using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -217,21 +234,21 @@ public void MergeDepthPassWithNoDepthPass() public void MergeNoDepthPassWithDepthPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // no depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // with depth using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -246,28 +263,28 @@ public void MergeNoDepthPassWithDepthPass() public void MergeMultiplePassesDifferentDepth() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // no depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // with depth using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // with no depth using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -282,21 +299,21 @@ public void MergeMultiplePassesDifferentDepth() public void MergeDifferentDepthFormatsBreaksPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // with different depth using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.extraDepthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.extraDepthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -312,19 +329,19 @@ public void MergePassWithWriteAllPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Merge pass, render with WriteAll in extra 0 using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.WriteAll); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.WriteAll); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -335,8 +352,8 @@ public void MergePassWithWriteAllPass() Assert.AreEqual(2, passes[0].numGraphPasses); // Validate attachments - Assert.AreEqual(buffers.depthBuffer.handle.index, passes[0].attachments[0].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, passes[0].attachments[1].handle.index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, passes[0].attachments[0].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, passes[0].attachments[1].handle.index); ref var depthAttachment = ref passes[0].attachments[0]; Assert.AreEqual(RenderBufferLoadAction.Load, depthAttachment.loadAction); @@ -350,20 +367,20 @@ public void MergeWriteAllPassWithReadPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to extra 0 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.WriteAll); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.WriteAll); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Read from extra 0 using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetInputAttachment(buffers.extraBuffers[0], 0, AccessFlags.Read); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -374,8 +391,8 @@ public void MergeWriteAllPassWithReadPass() Assert.AreEqual(2, passes[0].numGraphPasses); // Validate attachments - Assert.AreEqual(buffers.depthBuffer.handle.index, passes[0].attachments[0].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, passes[0].attachments[1].handle.index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, passes[0].attachments[0].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, passes[0].attachments[1].handle.index); ref var depthAttachment = ref passes[0].attachments[0]; Assert.AreEqual(RenderBufferLoadAction.Load, depthAttachment.loadAction); @@ -389,20 +406,20 @@ public void MergeReadPassWithWriteAllPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to 0,1 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Read); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.WriteAll); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.WriteAll); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -413,8 +430,8 @@ public void MergeReadPassWithWriteAllPass() Assert.AreEqual(2, passes[0].numGraphPasses); // Validate attachments - Assert.AreEqual(buffers.depthBuffer.handle.index, passes[0].attachments[0].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, passes[0].attachments[1].handle.index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, passes[0].attachments[0].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, passes[0].attachments[1].handle.index); ref var depthAttachment = ref passes[0].attachments[0]; Assert.AreEqual(RenderBufferLoadAction.Load, depthAttachment.loadAction); @@ -429,34 +446,34 @@ public void MergeDiscardPassWithWrite() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Discard extra 0 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Discard); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Discard); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Read extra 0 using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Read); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Write to extra 0 using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // break pass using another depth, Read extra 0 using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.extraDepthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Read); + builder.SetRenderAttachmentDepth(renderTargets.extraDepthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -467,9 +484,9 @@ public void MergeDiscardPassWithWrite() Assert.AreEqual(3, passes[0].numGraphPasses); // Validate attachments - Assert.AreEqual(buffers.depthBuffer.handle.index, passes[0].attachments[0].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, passes[0].attachments[1].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, passes[1].attachments[1].handle.index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, passes[0].attachments[0].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, passes[0].attachments[1].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, passes[1].attachments[1].handle.index); ref var depthAttachment = ref passes[0].attachments[0]; Assert.AreEqual(RenderBufferLoadAction.Load, depthAttachment.loadAction); @@ -488,12 +505,12 @@ public void MergeDiscardPassWithWrite() public void VerifyMergeStateAfterMergingPasses() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // First pass, not culled. { var builder = g.AddRasterRenderPass("TestPass0", out var passData); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); builder.Dispose(); @@ -510,7 +527,7 @@ public void VerifyMergeStateAfterMergingPasses() // Third pass, not culled. { var builder = g.AddRasterRenderPass("TestPass2", out var passData); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); builder.Dispose(); @@ -540,23 +557,23 @@ public void VerifyMergeStateAfterMergingPasses() } [Test] - public void NonFragmentUseBreaksPass() + public void NonFragmentSamplingBreaksPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.UseTexture(buffers.extraBuffers[0], AccessFlags.Read); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -567,34 +584,92 @@ public void NonFragmentUseBreaksPass() Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.PassBreakReason.NextPassReadsTexture, passes[0].breakAudit.reason); } + [Test] + public void FragmentAfterSamplingWithInputAttachmentBreaksPass() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 2, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + Assert.AreEqual(2, passes.Count); + Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.PassBreakReason.NextPassTargetsTexture, passes[0].breakAudit.reason); + } + + [Test] + public void FragmentAfterSamplingBreaksPass() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 1, AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 2, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + Assert.AreEqual(2, passes.Count); + Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.PassBreakReason.NextPassTargetsTexture, passes[0].breakAudit.reason); + } + [Test] public void NonRasterBreaksPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // No depth using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } - // Compute touches extraBuffers[0] + // Compute touches extraTextures[0] using (var builder = g.AddComputePass("ComputePass", out var passData)) { - builder.UseTexture(buffers.extraBuffers[0], AccessFlags.ReadWrite); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.ReadWrite); builder.SetRenderFunc((RenderGraphTestPassData data, ComputeGraphContext context) => { }); } // With depth using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 1, AccessFlags.Read); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 1, AccessFlags.Read); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -609,29 +684,29 @@ public void NonRasterBreaksPass() public void TooManyAttachmentsBreaksPass() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // 8 attachments using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); for (int i = 0; i < 6; i++) { - builder.SetRenderAttachment(buffers.extraBuffers[i], i, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[i], i, AccessFlags.Write); } - builder.SetRenderAttachment(buffers.backBuffer, 7, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 7, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // 2 additional attachments using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); for (int i = 0; i < 2; i++) { - builder.SetRenderAttachment(buffers.extraBuffers[i + 6], i, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[i + 6], i, AccessFlags.Write); } - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -646,16 +721,16 @@ public void TooManyAttachmentsBreaksPass() public void NativeSubPassesLimitNotExceeded() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Native subpasses limit is 8 so go above for (int i = 0; i < Rendering.RenderGraphModule.NativeRenderPassCompiler.NativePassCompiler.k_MaxSubpass + 2; i++) { using (var builder = g.AddRasterRenderPass($"TestPass_{i}", out var passData)) { - builder.SetInputAttachment(buffers.extraBuffers[1 - i % 2], 0); - builder.SetRenderAttachmentDepth(buffers.depthBuffer); - builder.SetRenderAttachment(buffers.extraBuffers[i % 2], 1); + builder.SetInputAttachment(renderTargets.extraTextures[1 - i % 2], 0); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer); + builder.SetRenderAttachment(renderTargets.extraTextures[i % 2], 1); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } } @@ -673,21 +748,21 @@ public void AllocateFreeInMergedPassesWorks() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to extra 0 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render extra bits to extra 1, this causes 1 to be allocated in pass 1 which will be the first sub pass of the merged native pass using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -695,19 +770,19 @@ public void AllocateFreeInMergedPassesWorks() // It's also the last time extra 1 is used so it gets freed using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 0, AccessFlags.ReadWrite); - builder.SetRenderAttachment(buffers.extraBuffers[2], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 1, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[2], 1, AccessFlags.Write); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -725,7 +800,7 @@ public void AllocateFreeInMergedPassesWorks() firstUsed.Add(res); Assert.AreEqual(1, firstUsed.Count); - Assert.AreEqual(buffers.extraBuffers[1].handle.index, firstUsed[0].index); + Assert.AreEqual(renderTargets.extraTextures[1].handle.index, firstUsed[0].index); // Pass 2 last used = { List lastUsed = new List(); @@ -734,7 +809,7 @@ public void AllocateFreeInMergedPassesWorks() lastUsed.Add(res); Assert.AreEqual(1, lastUsed.Count); - Assert.AreEqual(buffers.extraBuffers[1].handle.index, lastUsed[0].index); + Assert.AreEqual(renderTargets.extraTextures[1].handle.index, lastUsed[0].index); } @@ -743,22 +818,22 @@ public void MemorylessWorks() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to extra 0 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.ReadWrite); - builder.SetRenderAttachment(buffers.backBuffer, 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.backBuffer, 1, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -769,7 +844,7 @@ public void MemorylessWorks() Assert.AreEqual(3, passes[0].attachments.size); //1 extra + color + depth Assert.AreEqual(2, passes[0].numGraphPasses); - // Pass 0 : first used = {depthBuffer, extraBuffers[0]} + // Pass 0 : first used = {depthBuffer, extraTextures[0]} List firstUsed = new List(); ref var pass0Data = ref result.contextData.passData.ElementAt(0); foreach (ref readonly var res in pass0Data.FirstUsedResources(result.contextData)) @@ -777,18 +852,18 @@ public void MemorylessWorks() //Extra buffer 0 should be memoryless Assert.AreEqual(2, firstUsed.Count); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, firstUsed[1].index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, firstUsed[1].index); ref var info = ref result.contextData.UnversionedResourceData(firstUsed[1]); - Assert.AreEqual(true, info.memoryLess); + Assert.AreEqual(SystemInfo.supportsMemorylessTextures, info.memoryLess); - // Pass 1 : last used = {depthBuffer, extraBuffers[0], backBuffer} + // Pass 1 : last used = {depthBuffer, extraTextures[0], backBuffer} List lastUsed = new List(); ref var pass1Data = ref result.contextData.passData.ElementAt(1); foreach (var res in pass1Data.LastUsedResources(result.contextData)) lastUsed.Add(res); Assert.AreEqual(3, lastUsed.Count); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, lastUsed[1].index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, lastUsed[1].index); } [Test] @@ -796,26 +871,26 @@ public void InputAttachmentsWork() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to extra 0,1,2 using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[1], 1, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[2], 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render to final buffer using extra 0 as attachment using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.ReadWrite); - builder.SetRenderAttachment(buffers.backBuffer, 1, AccessFlags.Write); - builder.SetInputAttachment(buffers.extraBuffers[0], 0, AccessFlags.Read); - builder.SetInputAttachment(buffers.extraBuffers[1], 1, AccessFlags.Read); - builder.SetInputAttachment(buffers.extraBuffers[2], 2, AccessFlags.Read); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.backBuffer, 1, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); + builder.SetInputAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Read); + builder.SetInputAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -827,11 +902,11 @@ public void InputAttachmentsWork() Assert.AreEqual(2, nativePasses[0].numGraphPasses); // Validate attachments - Assert.AreEqual(buffers.depthBuffer.handle.index, nativePasses[0].attachments[0].handle.index); - Assert.AreEqual(buffers.extraBuffers[0].handle.index, nativePasses[0].attachments[1].handle.index); - Assert.AreEqual(buffers.extraBuffers[1].handle.index, nativePasses[0].attachments[2].handle.index); - Assert.AreEqual(buffers.extraBuffers[2].handle.index, nativePasses[0].attachments[3].handle.index); - Assert.AreEqual(buffers.backBuffer.handle.index, nativePasses[0].attachments[4].handle.index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, nativePasses[0].attachments[0].handle.index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, nativePasses[0].attachments[1].handle.index); + Assert.AreEqual(renderTargets.extraTextures[1].handle.index, nativePasses[0].attachments[2].handle.index); + Assert.AreEqual(renderTargets.extraTextures[2].handle.index, nativePasses[0].attachments[3].handle.index); + Assert.AreEqual(renderTargets.backBuffer.handle.index, nativePasses[0].attachments[4].handle.index); // Sub Pass 0 ref var subPass = ref result.contextData.nativeSubPassData.ElementAt(nativePasses[0].firstNativeSubPass); @@ -857,7 +932,7 @@ public void InputAttachmentsWork() public void ImportParametersWork() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Import with parameters var backBuffer = BuiltinRenderTextureType.CameraTarget; @@ -885,16 +960,16 @@ public void ImportParametersWork() // Compute does something or other using (var builder = g.AddComputePass("ComputePass", out var passData)) { - builder.UseTexture(buffers.extraBuffers[0], AccessFlags.ReadWrite); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.ReadWrite); builder.SetRenderFunc((RenderGraphTestPassData data, ComputeGraphContext context) => { }); } // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.ReadWrite); builder.SetRenderAttachment(importedTexture, 1, AccessFlags.Write); - builder.SetRenderAttachment(buffers.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -925,12 +1000,12 @@ public void ImportParametersWork() public void FencesWork() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Pass #1: Render pass writing to backbuffer using (var builder = g.AddRasterRenderPass("#1 RenderPass", out _)) { - builder.UseTexture(buffers.backBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.backBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -938,21 +1013,21 @@ public void FencesWork() using (var builder = g.AddComputePass("#2 AsyncComputePass", out _)) { builder.EnableAsyncCompute(true); - builder.UseTexture(buffers.backBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.backBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, ComputeGraphContext context) => { }); } // Pass #3: Render pass writing to backbuffer using (var builder = g.AddRasterRenderPass("#3 RenderPass", out _)) { - builder.UseTexture(buffers.backBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.backBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Pass #4: Render pass writing to backbuffer using (var builder = g.AddRasterRenderPass("#4 RenderPass", out _)) { - builder.UseTexture(buffers.backBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.backBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -980,7 +1055,7 @@ public void FencesWork() public void MaxReadersAndMaxVersionsAreCorrectForBuffers() { var g = AllocateRenderGraph(); - var rendertargets = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); var desc = new BufferDesc(1024, 16); var buffer = g.CreateBuffer(desc); @@ -1017,21 +1092,21 @@ public void MaxReadersAndMaxVersionsAreCorrectForBuffers() public void MaxReadersAndMaxVersionsAreCorrectForTextures() { var g = AllocateRenderGraph(); - var rendertargets = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Render something to extra 0 and write uav using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(rendertargets.depthBuffer, AccessFlags.Write); - builder.UseTexture(rendertargets.extraBuffers[0], AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } // Render extra bits to 0 reading from the uav using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(rendertargets.depthBuffer, AccessFlags.Read); - builder.UseTexture(rendertargets.extraBuffers[0], AccessFlags.ReadWrite); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Read); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.ReadWrite); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -1039,17 +1114,17 @@ public void MaxReadersAndMaxVersionsAreCorrectForTextures() using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { builder.AllowPassCulling(false); - builder.UseTexture(rendertargets.extraBuffers[0], AccessFlags.Read); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); - // Resources with the biggest MaxReaders are extraBuffers[0] and depthBuffer (both being equal): + // Resources with the biggest MaxReaders are extraTextures[0] and depthBuffer (both being equal): // 1 implicit read (TestPass0) + 2 explicit read (TestPass1 & TestPass2) + 1 for the offset Assert.AreEqual(result.contextData.resources.MaxReaders, 4); - // The resource with the biggest MaxVersion is extraBuffers[0]: + // The resource with the biggest MaxVersion is extraTextures[0]: // 1 explicit write (TestPass0) + 1 explicit read-write (TestPass1) + 1 for the offset Assert.AreEqual(result.contextData.resources.MaxVersions, 3); } @@ -1058,7 +1133,7 @@ public void MaxReadersAndMaxVersionsAreCorrectForTextures() public void MaxReadersAndMaxVersionsAreCorrectForBuffersMultiplePasses() { var g = AllocateRenderGraph(); - var rendertargets = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); var desc = new BufferDesc(1024, 16); var buffer = g.CreateBuffer(desc); @@ -1100,7 +1175,7 @@ public void MaxReadersAndMaxVersionsAreCorrectForBuffersMultiplePasses() public void BuffersWork() { var g = AllocateRenderGraph(); - var rendertargets = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); var desc = new BufferDesc(1024, 16); var buffer = g.CreateBuffer(desc); @@ -1108,8 +1183,8 @@ public void BuffersWork() // Render something to extra 0 and write uav using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachmentDepth(rendertargets.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(rendertargets.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.UseBufferRandomAccess(buffer, 1, AccessFlags.ReadWrite); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -1117,8 +1192,8 @@ public void BuffersWork() // Render extra bits to 0 reading from the uav using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachmentDepth(rendertargets.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(rendertargets.extraBuffers[0], 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); builder.UseBuffer(buffer, AccessFlags.Read); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -1126,9 +1201,9 @@ public void BuffersWork() // Render to final buffer using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) { - builder.UseTexture(rendertargets.extraBuffers[0]); - builder.SetRenderAttachment(rendertargets.backBuffer, 2, AccessFlags.Write); - builder.SetRenderAttachmentDepth(rendertargets.depthBuffer, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0]); + builder.SetRenderAttachment(renderTargets.backBuffer, 2, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); } @@ -1140,9 +1215,9 @@ public void BuffersWork() var firstUsedList = pass0Data.FirstUsedResources(result.contextData).ToArray(); Assert.AreEqual(3, firstUsedList.Length); - Assert.AreEqual(rendertargets.depthBuffer.handle.index, firstUsedList[0].index); + Assert.AreEqual(renderTargets.depthBuffer.handle.index, firstUsedList[0].index); Assert.AreEqual(RenderGraphResourceType.Texture, firstUsedList[0].type); - Assert.AreEqual(rendertargets.extraBuffers[0].handle.index, firstUsedList[1].index); + Assert.AreEqual(renderTargets.extraTextures[0].handle.index, firstUsedList[1].index); Assert.AreEqual(RenderGraphResourceType.Texture, firstUsedList[1].type); Assert.AreEqual(buffer.handle.index, firstUsedList[2].index); Assert.AreEqual(RenderGraphResourceType.Buffer, firstUsedList[2].type); @@ -1167,7 +1242,7 @@ public void BuffersWork() public void ResolveMSAAImportColor() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // Import with parameters // Depth @@ -1232,7 +1307,7 @@ public void ResolveMSAAImportColor() public void TransientTexturesCantBeReused() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); var textureTransientHandle = TextureHandle.nullHandle; // Render something to textureTransientHandle, created locally in the pass. @@ -1274,7 +1349,7 @@ public void TransientTexturesCantBeReused() public void TransientBuffersCantBeReused() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); var bufferTransientHandle = BufferHandle.nullHandle; // Render something to textureTransientHandle, created locally in the pass. @@ -1306,7 +1381,7 @@ public void TransientBuffersCantBeReused() public void ChangingGlobalStateDisablesCulling() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); // First pass ; culling should be set to false after calling AllowGlobalStateModification. { @@ -1338,35 +1413,51 @@ public void ChangingGlobalStateDisablesCulling() } [Test] - public void DecreaseResourceVersionIfLastPassIsCulled() + public void DecreaseResourceVersion_WhenAllProducersExceptFirstAreCulled() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); - // Bumping version of extraBuffer within RG to 1 as we write to it in first pass + // Bumping version of exteraTexture within RG to 1 as we write to it in first pass using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } - // Bumping version of extraBuffer within RG to 2 as we write to it in second pass + // Bumping version of exteraTexture within RG to 2 as we write to it in second pass using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(true); } - // First pass is preserved as requested but second pass is culled + // Bumping version of extraBuffer within RG to 3 as we write to it in third pass + using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + // Bumping version of extraBuffer within RG to 4 as we write to it in forth pass + using (var builder = g.AddRasterRenderPass("TestPass3", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + // First pass is preserved as requested but other passes are culled var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); var passes = result.contextData.GetNativePasses(); - // Second pass has been culled + // Only the first pass is preserved Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 1); - // extraBuffer version has decreased to 1 as it is only used by the first pass - Assert.AreEqual(passes[0].attachments[0].handle.version, 1); + // extraTextures version has decreased to 1 as it is only used by the first pass + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[0].handle).latestVersionNumber, 1); } [Test] @@ -1432,68 +1523,68 @@ int CountGCAllocs(Action action) public void UpdateSubpassAttachmentIndices_WhenDepthAttachmentIsAdded() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); using (var builder = g.AddRasterRenderPass("NoDepth0_Subpass0", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [extraBuffers[0]] + // attachments: [extraTextures[0]] // subpass 0: color outputs : [0] using (var builder = g.AddRasterRenderPass("NoDepth1_Subpass0", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[0], 0); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [extraBuffers[0]] + // attachments: [extraTextures[0]] // subpass 0: color outputs : [0] using (var builder = g.AddRasterRenderPass("NoDepth2_Subpass1", out var passData)) { - builder.SetRenderAttachment(buffers.extraBuffers[1], 0); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [extraBuffers[0], extraBuffers[1]] + // attachments: [extraTextures[0], extraTextures[1]] // subpass 0: color outputs : [0] // subpass 1: color outputs : [1] using (var builder = g.AddRasterRenderPass("NoDepth3_Subpass2", out var passData)) { - builder.SetInputAttachment(buffers.extraBuffers[0], 0); - builder.SetInputAttachment(buffers.extraBuffers[1], 1); - builder.SetRenderAttachment(buffers.extraBuffers[2], 0); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0); + builder.SetInputAttachment(renderTargets.extraTextures[1], 1); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [extraBuffers[0], extraBuffers[1], extraBuffers[2]] + // attachments: [extraTextures[0], extraTextures[1], extraTextures[2]] // subpass 0: color outputs : [0] // subpass 1: color outputs : [1] // subpass 2: color outputs : [2], inputs : [0, 1] using (var builder = g.AddRasterRenderPass("Depth_Subpass3", out var passData)) { - builder.SetInputAttachment(buffers.extraBuffers[0], 0); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[3], 0); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[3], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [depthBuffer, extraBuffers[1], extraBuffers[2], extraBuffers[0], extraBuffers[3]] + // attachments: [depthBuffer, extraTextures[1], extraTextures[2], extraTextures[0], extraTextures[3]] // subpass 0: color outputs : [0 -> 3] // subpass 1: color outputs : [1] // subpass 2: color outputs : [2], inputs : [0 -> 3, 1] @@ -1506,11 +1597,11 @@ public void UpdateSubpassAttachmentIndices_WhenDepthAttachmentIsAdded() Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 5 && passes[0].numNativeSubPasses == 4); // Depth is the first attachment - Assert.IsTrue(passes[0].attachments[0].handle.index == buffers.depthBuffer.handle.index); - Assert.IsTrue(passes[0].attachments[1].handle.index == buffers.extraBuffers[1].handle.index); - Assert.IsTrue(passes[0].attachments[2].handle.index == buffers.extraBuffers[2].handle.index); - Assert.IsTrue(passes[0].attachments[3].handle.index == buffers.extraBuffers[0].handle.index); - Assert.IsTrue(passes[0].attachments[4].handle.index == buffers.extraBuffers[3].handle.index); + Assert.IsTrue(passes[0].attachments[0].handle.index == renderTargets.depthBuffer.handle.index); + Assert.IsTrue(passes[0].attachments[1].handle.index == renderTargets.extraTextures[1].handle.index); + Assert.IsTrue(passes[0].attachments[2].handle.index == renderTargets.extraTextures[2].handle.index); + Assert.IsTrue(passes[0].attachments[3].handle.index == renderTargets.extraTextures[0].handle.index); + Assert.IsTrue(passes[0].attachments[4].handle.index == renderTargets.extraTextures[3].handle.index); // Check first subpass is correctly updated ref var subPassDesc0 = ref result.contextData.nativeSubPassData.ElementAt(0); @@ -1543,32 +1634,32 @@ public void UpdateSubpassAttachmentIndices_WhenDepthAttachmentIsAdded() public void UpdateShadingRateImageIndex_WhenDepthAttachmentIsAdded() { var g = AllocateRenderGraph(); - var buffers = ImportAndCreateBuffers(g); + var renderTargets = ImportAndCreateRenderTargets(g); using (var builder = g.AddRasterRenderPass("NoDepth_Subpass0", out var passData)) { - builder.SetShadingRateImageAttachment(buffers.extraBuffers[0]); - builder.SetRenderAttachment(buffers.extraBuffers[1], 0); + builder.SetShadingRateImageAttachment(renderTargets.extraTextures[0]); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [extraBuffers[0], extraBuffers[1]] + // attachments: [extraTextures[0], extraTextures[1]] // shading rate image : [0] // subpass 0: color outputs : [1] using (var builder = g.AddRasterRenderPass("Depth_Subpass1", out var passData)) { - builder.SetShadingRateImageAttachment(buffers.extraBuffers[0]); - builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); - builder.SetRenderAttachment(buffers.extraBuffers[2], 0); + builder.SetShadingRateImageAttachment(renderTargets.extraTextures[0]); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 0); builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); builder.AllowPassCulling(false); } // Render Pass - // attachments: [depthBuffer, extraBuffers[1], extraBuffers[0], extraBuffers[2]] + // attachments: [depthBuffer, extraTextures[1], extraTextures[0], extraTextures[2]] // shading rate image : [0 -> 2] // subpass 0: color outputs : [1] // subpass 1: color outputs : [3] @@ -1580,14 +1671,318 @@ public void UpdateShadingRateImageIndex_WhenDepthAttachmentIsAdded() Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 2 && passes[0].numNativeSubPasses == 2); // Depth is the first attachment - Assert.IsTrue(passes[0].attachments[0].handle.index == buffers.depthBuffer.handle.index); - Assert.IsTrue(passes[0].attachments[1].handle.index == buffers.extraBuffers[1].handle.index); - Assert.IsTrue(passes[0].attachments[2].handle.index == buffers.extraBuffers[0].handle.index); - Assert.IsTrue(passes[0].attachments[3].handle.index == buffers.extraBuffers[2].handle.index); + Assert.IsTrue(passes[0].attachments[0].handle.index == renderTargets.depthBuffer.handle.index); + Assert.IsTrue(passes[0].attachments[1].handle.index == renderTargets.extraTextures[1].handle.index); + Assert.IsTrue(passes[0].attachments[2].handle.index == renderTargets.extraTextures[0].handle.index); + Assert.IsTrue(passes[0].attachments[3].handle.index == renderTargets.extraTextures[2].handle.index); // Check Shading Rate Image index is correctly updated - Assert.IsTrue(passes[0].shadingRateImageIndex == buffers.extraBuffers[0].handle.index); + Assert.IsTrue(passes[0].shadingRateImageIndex == renderTargets.extraTextures[0].handle.index); } */ + + [Test] + public void UnusedResourceCulling_CullProducer_WhenVersionsAreNotExplicitlyRead() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + // Bumping version of extraBuffer within RG to 1 as we write to it in first pass + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + // Bumping version of extraBuffer within RG to 2 as we write to it in second pass + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + // Bumping version of extraBuffer within RG to 3 as we write to it in third pass + using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + // Bumping version of extraBuffer within RG to 4 as we write to it in fourth pass + using (var builder = g.AddRasterRenderPass("TestPass3", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(false); // Not culled! + } + + // Bumping version of extraBuffer within RG to 5 as we write to it in fifth pass + using (var builder = g.AddRasterRenderPass("TestPass4", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(true); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + // All passes are only writing different versions of a non-imported texture that is never explicitly read by anyone + // Only fourth pass is preserved as requested but other passes are culled + Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 1); + // TestPass3 is the first pass needing extraTextures[0] so this is the pass allocating it + Assert.IsTrue(result.contextData.passData.ElementAt(3).FirstUsedResources(result.contextData).Length == 1); + // extraBuffer version has decreased to 4 as it is written by the fourth pass + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[0].handle).latestVersionNumber, 4); + } + + [Test] + public void UnusedResourceCulling_CullProducer_WhenNoneOfItsWrittenResourcesAreExplicitlyRead() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + // This pass implicitly reads version 0 of extraTextures[0] and writes its version 1 that will be implicitly read in the next pass - dependency + // It also explicitly reads version 0 of extraTextures[1] and writes its version 1 but none reads it - no side effect + // It also implicitly reads version 0 of depthBuffer that is imported in RG but don't write it - no side effect + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass implicitly reads version 1 of extraTextures[1] and writes its version 2 that none reads - no side effect + // It also implicitly reads version 0 of extraTextures[2] and writes its version 1 that will be explicitly read in the next pass - dependency + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass explicitly reads version 1 of extraTextures[2] + // We explicitly request this pass not to be culled, so it will be preserved + using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(false); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + // - TestPass2 can NOT be culled + // - TestPass1 can NOT be culled as it is an explicit dependency of TestPass2 through extraTextures[2] + // - TestPass0 can be culled as it is an implicit dependency of TestPass1 through extraTextures[1] but none reads extraTextures[1] so we can break this dependency + Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 2); + // extraBuffer[1] latest version remains at 2 but none writes version 1, it is okay since none reads it + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[0].handle).latestVersionNumber, 2); + // extraBuffer[2] latest version remains at 1 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[1].handle).latestVersionNumber, 1); + } + + [Test] + public void UnusedResourceCulling_DoNotCullProducer_WhenOneOfItsWrittenResourcesIsExplicitlyRead() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + // This pass implicitly reads version 0 of extraTextures[0] and writes its version 1 that will be explicitly read in the next pass - dependency + // It also explicitly reads version 0 of extraTextures[1] and writes its version 1 but none reads it - no side effect + // It also implicitly reads version 0 of depthBuffer that is imported in RG but don't write it - no side effect + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass explicitly reads version 1 of extraTextures[1] and writes its version 2 that none reads - no side effect + // It also implicitly reads version 0 of extraTextures[2] and writes its version 1 that will be explicitly read in the next pass - dependency + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[1], 1, AccessFlags.ReadWrite); + builder.SetRenderAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass explicitly reads version 1 of extraTextures[2] + // We explicitly request this pass not to be culled, so it will be preserved + using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[2], 2, AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(false); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + // All passes are preserved, + // - TestPass2 can't be culled + // - TestPass1 is an explicit dependency of TestPass2 through extraTextures[2] + // - TestPass0 is an explicit dependency of TestPass1 through extraTextures[1] + Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 3); + // depth last version is 0 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[0].handle).latestVersionNumber, 0); + // extraBuffer[0] latest version remains at 1 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[1].handle).latestVersionNumber, 1); + // extraBuffer[1] latest version remains at 2 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[2].handle).latestVersionNumber, 2); + // extraBuffer[2] latest version remains at 1 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[3].handle).latestVersionNumber, 1); + } + + [Test] + public void UnusedResourceCulling_CullProducer_WhenNextVersionOfProducedResourceIsWrittenAll() + { + var g = AllocateRenderGraph(); + var renderTargets = ImportAndCreateRenderTargets(g); + + // This pass implicitly reads version 0 of extraTextures[0] and writes its version 1 that will be explicitly read in the next pass - dependency + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass explicitly reads version 1 of extraTextures[0] - no side effect + using (var builder = g.AddRasterRenderPass("TestPass1", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass implicitly reads version 1 of extraTextures[0] and writes its version 2 that will not be read in the next pass - no side effect + using (var builder = g.AddRasterRenderPass("TestPass2", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + + // This pass writes all version 3 of extraTextures[2] + // We explicitly request this pass not to be culled, so it will be preserved + using (var builder = g.AddRasterRenderPass("TestPass3", out var passData)) + { + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.WriteAll); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + builder.AllowPassCulling(false); + } + + var result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + // Only last pass is preserved + // - TestPass3 can't be culled + // - TestPass2 can be culled as it is has no side effect and writes extraTextures[0] at version 2 that none reads as TestPass3 rewrites all of extraTextures[0] + // - TestPass1 can be culled as it is has no side effect and only reads extraTextures[0] + // - TestPass0 can be culled as it is an implicit dependency of TestPass1 through extraTextures[0] but TestPass1 will be culled + Assert.IsTrue(passes != null && passes.Count == 1 && passes[0].numGraphPasses == 1); + // extraBuffer[0] latest version remains at 2 + Assert.AreEqual(result.contextData.UnversionedResourceData(passes[0].attachments[0].handle).latestVersionNumber, 3); + } + + // Test using a texture as both a texture and render attachment, one will require topleft and one bottom left so this should throw. + [Test] + public void TextureUVOrigin_CheckInvalidMixedUVOriginUseTextureCompiler() + { + var g = AllocateRenderGraph(); + g.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + + var renderTargets = ImportAndCreateRenderTargets(g); + + // Render something to 0 with a TopLeft depth and color attachment and a BottomLeft texture + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + Assert.Throws(() => + { + NativePassCompiler result = g.CompileNativeRenderGraph(g.ComputeGraphHash()); + }); + } + + // Test mixing bottom left and top left resources in the same renderpass and check we throw. + [Test] + public void TextureUVOrigin_CheckInvalidMixedUVOriginDirect_DepthCheck() + { + var g = AllocateRenderGraph(); + g.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + + var renderTargets = ImportAndCreateRenderTargets(g); + + // Render something to 0 with a BottomLeft depth attachment and a TopLeft color target + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + Assert.Throws(() => + { + builder.SetRenderAttachmentDepth(renderTargets.extraDepthBufferBottomLeft, AccessFlags.Write); + }); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + } + + // Test mixing bottom left and top left resources in the same renderpass and check we throw. + [Test] + public void TextureUVOrigin_CheckInvalidMixedUVOriginDirect_ColorCheck() + { + var g = AllocateRenderGraph(); + g.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + + var renderTargets = ImportAndCreateRenderTargets(g); + + // Render something to 0 with a BottomLeft depth attachment and a TopLeft color target + using (var builder = g.AddRasterRenderPass("TestPass0", out var passData)) + { + builder.SetRenderAttachmentDepth(renderTargets.extraDepthBufferBottomLeft, AccessFlags.Write); + Assert.Throws(() => + { + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + }); + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + } + + // Test mixing bottom left and top left resources in the same renderpass and check we throw. + [Test] + public void TextureUVOrigin_CheckInvalidMixedUVOriginDirect_InputCheck() + { + var g = AllocateRenderGraph(); + g.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + + var renderTargets = ImportAndCreateRenderTargets(g); + + // Render something to 0 with a TopLeft depth attachment, a TopLeft color target, TopLeft input attachment and a BottomLeft input attachment + using (var builder = g.AddRasterRenderPass("TestPass02", out var passData)) + { + builder.SetInputAttachment(renderTargets.extraTextureBottomLeft, 0, AccessFlags.Read); + Assert.Throws(() => + { + builder.SetInputAttachment(renderTargets.extraTextureTopLeft, 0, AccessFlags.Read); + }); + Assert.Throws(() => + { + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + }); + Assert.Throws(() => + { + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + }); + + builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { }); + } + } } } diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing.meta new file mode 100644 index 00000000000..744cd8c729b --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fd666911d8fe6a45863ddd7dec1be72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs new file mode 100644 index 00000000000..39e21626f5f --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs @@ -0,0 +1,153 @@ +using NUnit.Framework; +using System; +using System.IO; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.PathTracing.Integration; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering; +using UnityEngine.Rendering.UnifiedRayTracing; +using UnityEditor; + +namespace UnityEngine.PathTracing.Tests +{ + [TestFixture("Compute")] + [TestFixture("Hardware")] + internal class ResourceCacheTests + { + RayTracingBackend _backend; + RayTracingContext _context; + CommandBuffer m_Cmd; + UVFallbackBufferBuilder m_UVFBBuilder; + LightmapIntegrationResourceCache m_ResourceCache; + ChartRasterizer m_ChartRasterizer; + ChartRasterizer.Buffers m_ChartRasterizerBuffers; + + public ResourceCacheTests(string backendAsString) + { + _backend = Enum.Parse(backendAsString); + } + + void CreateRayTracingResources() + { + var resources = new RayTracingResources(); + resources.Load(); + _context = new RayTracingContext(_backend, resources); + m_UVFBBuilder = new UVFallbackBufferBuilder(); + Material uvFbMaterial = new(Shader.Find("Hidden/UVFallbackBufferGeneration")); + + m_UVFBBuilder.Prepare(uvFbMaterial); + m_ResourceCache = new LightmapIntegrationResourceCache(); + + ChartRasterizer.LoadShaders(out var software, out var hardware); + m_ChartRasterizer = new ChartRasterizer(software, hardware); + + int maxIndexCount = 6; // 2 triangles + m_ChartRasterizerBuffers = new ChartRasterizer.Buffers() + { + vertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, UnsafeUtility.SizeOf()), + vertexToOriginalVertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, sizeof(uint)), + vertexToChartID = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxIndexCount, sizeof(uint)), + }; + } + + void DisposeRayTracingResources() + { + m_ResourceCache?.Dispose(); + m_UVFBBuilder?.Dispose(); + m_ChartRasterizer?.Dispose(); + m_ChartRasterizerBuffers.vertex?.Dispose(); + m_ChartRasterizerBuffers.vertexToOriginalVertex?.Dispose(); + m_ChartRasterizerBuffers.vertexToChartID?.Dispose(); + _context?.Dispose(); + } + + [SetUp] + public void SetUp() + { + if (!SystemInfo.supportsRayTracing && _backend == RayTracingBackend.Hardware) + { + Assert.Ignore("Cannot run test on this Graphics API. Hardware RayTracing is not supported"); + } + + if (!SystemInfo.supportsComputeShaders && _backend == RayTracingBackend.Compute) + { + Assert.Ignore("Cannot run test on this Graphics API. Compute shaders are not supported"); + } + + if (SystemInfo.graphicsDeviceName.Contains("llvmpipe")) + { + Assert.Ignore("Cannot run test on this device (Renderer: llvmpipe (LLVM 10.0.0, 128 bits)). Tests are disabled because they fail on some platforms (that do not support 11 SSBOs). Once we do not run Ubuntu 18.04 try removing this"); + } + + CreateRayTracingResources(); + m_Cmd = new(); + } + + [TearDown] + public void TearDown() + { + DisposeRayTracingResources(); + m_Cmd?.Dispose(); + } + + [Test] + public void ResourceCache_AddInstance_ResourcesAreAdded() + { + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + BakeInstance instance = new(); + instance.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance[] instances = { instance }; + Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache."); + Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh."); + Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure."); + Assert.AreEqual(1, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has one uv fallback buffer."); + } + + [Test] + public void ResourceCache_AddTwoInstancesSameMesh_OnlyOneSetOfResourcesAreAdded() + { + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + BakeInstance instance1 = new(); + instance1.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance instance2 = new(); + instance2.Build(mesh, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance[] instances = { instance1, instance2 }; + Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache."); + Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh."); + Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure."); + Assert.AreEqual(1, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has one uv fallback buffer."); + } + + [Test] + public void ResourceCache_AddTwoInstancesDifferentMesh_TwoSetsOfResourcesAreAdded() + { + Mesh mesh1 = TestUtils.CreateSingleTriangleMesh(); + Mesh mesh2 = TestUtils.CreateQuadMesh(); + BakeInstance instance1 = new(); + instance1.Build(mesh1, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance instance2 = new(); + instance2.Build(mesh2, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance[] instances = { instance1, instance2 }; + Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache."); + Assert.AreEqual(2, m_ResourceCache.UVMeshCount(), "Expected that the cache has 2 uv meshes."); + Assert.AreEqual(2, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has two uv acceleration structures."); + Assert.AreEqual(2, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has two uv fallback buffers."); + } + + [Test] + public void ResourceCache_AddTwoInstanceSameMeshDifferentResolution_TwoFallbackBuffersAreAdded() + { + Mesh mesh1 = TestUtils.CreateSingleTriangleMesh(); + Mesh mesh2 = mesh1; + BakeInstance instance1 = new(); + instance1.Build(mesh1, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(5, 5), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance instance2 = new(); + instance2.Build(mesh2, new Vector4(1, 1, 0, 0), new Vector4(1, 1, 0, 0), new Vector2Int(10, 10), Vector2Int.zero, Matrix4x4.identity, true, LodIdentifier.Invalid, 0); + BakeInstance[] instances = { instance1, instance2 }; + Assert.IsTrue(m_ResourceCache.AddResources(instances, _context, m_Cmd, m_UVFBBuilder), "Expected that the instance could be added to cache."); + Assert.AreEqual(1, m_ResourceCache.UVMeshCount(), "Expected that the cache has one uv mesh."); + Assert.AreEqual(1, m_ResourceCache.UVAccelerationStructureCount(), "Expected that the cache has one uv acceleration structure."); + Assert.AreEqual(2, m_ResourceCache.UVFallbackBufferCount(), "Expected that the cache has two uv fallback buffers due to the different resolution."); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs.meta new file mode 100644 index 00000000000..4bc1f2bc1ac --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/ResourceCacheTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0a029a5784e42b24ea95dcd61b5addab \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs new file mode 100644 index 00000000000..398aaad85d7 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs @@ -0,0 +1,81 @@ +using NUnit.Framework; +using System; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; + +namespace UnityEngine.PathTracing.Tests +{ + internal class SegmentedReductionTests + { + SegmentedReduction reduction; + + [SetUp] + public void SetUp() + { + reduction = new SegmentedReduction(SegmentedReduction.LoadShader()); + } + + [Test] + [TestCase(1u, 64u, 64u)] // Test some small problem size + [TestCase(3u, 64u, 64u)] + [TestCase(7u, 64u, 64u)] + [TestCase(1u, 1697u, 1201u)] // Test some prime numbers - these are the tricky cases + [TestCase(3u, 1697u, 1201u)] + [TestCase(7u, 1697u, 1201u)] + [TestCase(1u, 193u, 43201u)] // Test some large prime numbers for size + [TestCase(3u, 193u, 43201u)] + [TestCase(7u, 193u, 43201u)] + [TestCase(1u, 43201u, 193u)] // ... and for number of sums + [TestCase(3u, 43201u, 193u)] + [TestCase(7u, 43201u, 193u)] + public void SegmentedReduction_WithAnyData_MatchesReferenceImplementation(uint stride, uint numSums, uint sumSize) + { + // Generate some random data to sum + System.Random r = new System.Random(1337); + float[] sums = new float[numSums * sumSize * stride]; + for (uint i = 0; i < sums.Length; i++) + { + sums[i] = (float)r.NextDouble() * 10.0f; + } + + // Super simple manual sum on CPU + float[] referenceSums = new float[numSums * stride]; + for (uint i = 0; i < numSums; i++) + { + for (uint j = 0; j < sumSize; j++) + { + for (int k = 0; k < stride; k++) + { + referenceSums[i * stride + k] += sums[(i * sumSize + j) * stride + k]; + } + } + } + + // Now sum on GPU + using var bufferToSum = new GraphicsBuffer(GraphicsBuffer.Target.Structured, sums.Length, sizeof(float)); + bufferToSum.SetData(sums); + + uint scratchSize = SegmentedReduction.GetScratchBufferSizeInDwords(sumSize, stride, numSums); + using var scratchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)scratchSize, sizeof(float)); + + using var outputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, referenceSums.Length, sizeof(float)); + + using var cmd = new CommandBuffer(); + reduction.TwoPassSegmentedReduction(cmd, sumSize, stride, numSums, 0, 0, bufferToSum, scratchBuffer, outputBuffer, true); + Graphics.ExecuteCommandBuffer(cmd); + + float[] gpuSums = new float[referenceSums.Length]; + outputBuffer.GetData(gpuSums); + + // Compare results + for (uint i = 0; i < referenceSums.Length; i++) + { + float difference = Math.Abs(referenceSums[i] - gpuSums[i]); + float error = difference / referenceSums[i]; + // Floating point addition is not associative, so we can't expect perfect results. + // We allow for a maximum error of 0.01%. + Assert.Less(error, 0.0001f, $"Value at {i} didn't match reference result!"); + } + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs.meta new file mode 100644 index 00000000000..3fcfe8830d5 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/SegmentedReductionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d5e0f91dbc24fb9488b44bb8a48f13e9 +timeCreated: 1696318367 diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs new file mode 100644 index 00000000000..c4f6fddc894 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs @@ -0,0 +1,853 @@ +using NUnit.Framework; +using System; +using UnityEngine.Rendering.UnifiedRayTracing; +using UnityEngine.PathTracing.Integration; +using System.Collections.Generic; +using UnityEngine.Rendering; +using System.IO; +using System.Linq; +using UnityEditor; +using Unity.Mathematics; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.PathTracing.Lightmapping; +using UnityEngine.Rendering.Sampling; + +namespace UnityEngine.PathTracing.Tests +{ + internal static class TestUtils + { + public static Mesh CreateSingleTriangleMesh() + { + Mesh mesh = new Mesh(); + + Vector3[] vertices = { + new(-0.5f, -0.5f, 0), + new(1.0f, -0.5f, 0), + new(-0.5f, 1.0f, 0) + }; + mesh.vertices = vertices; + + Vector3[] normals = { + -Vector3.forward, + -Vector3.forward, + -Vector3.forward + }; + mesh.normals = normals; + + Vector2[] uv = { + new(0, 1), + new(1, 1), + new(0, 0) + }; + mesh.uv = uv; + + int[] tris = { + 0, 2, 1 + }; + mesh.triangles = tris; + + return mesh; + } + + public static Mesh CreateQuadMesh() + { + Mesh mesh = new Mesh(); + + Vector3[] vertices = { + new(-0.5f, -0.5f, 0.0f), + new(0.5f, -0.5f, 0.0f), + new(-0.5f, 0.5f, 0.0f), + new(0.5f, 0.5f, 0.0f) + }; + mesh.vertices = vertices; + + Vector3[] normals = { + -Vector3.forward, + -Vector3.forward, + -Vector3.forward, + -Vector3.forward + }; + mesh.normals = normals; + + Vector2[] uv = { + new(0, 0), + new(1, 0), + new(0, 1), + new(1, 1) + }; + mesh.uv = uv; + + int[] tris = { + 0, 2, 1, + 2, 3, 1 + }; + mesh.triangles = tris; + + return mesh; + } + + public static void ScaleUVs(Vector2 scale, ref Mesh mesh) + { + int dimension = mesh.GetVertexAttributeDimension(VertexAttribute.TexCoord0); + Assert.AreEqual(2, dimension, $"Expected that uv0 channel has dimension 2."); + Vector2[] uvs = mesh.uv; + for (int i = 0; i < uvs.Length; ++i) + { + uvs[i] *= scale; + } + mesh.uv = uvs; + } + + public static void TranslateUVs(Vector2 offset, ref Mesh mesh) + { + int dimension = mesh.GetVertexAttributeDimension(VertexAttribute.TexCoord0); + Assert.AreEqual(2, dimension, $"Expected that uv0 channel has dimension 2."); + Vector2[] uvs = mesh.uv; + for (int i = 0; i < uvs.Length; ++i) + { + uvs[i] += offset; + } + mesh.uv = uvs; + } + + public static void AssertThatUVsAreNormalized(Vector3[] uvs, float tolerance = 0.000001f) + { + for (int i = 0; i < uvs.Length; ++i) + { + Assert.IsTrue(uvs[i].x <= 1.0f + tolerance, $"Expected that the output uvs are normalized uv.x {uvs[i].x}."); + Assert.IsTrue(uvs[i].x >= 0.0f - tolerance, $"Expected that the output uvs are normalized uv.x {uvs[i].x}."); + Assert.IsTrue(uvs[i].y <= 1.0f + tolerance, $"Expected that the output uvs are normalized uv.y {uvs[i].y}."); + Assert.IsTrue(uvs[i].y >= 0.0f - tolerance, $"Expected that the output uvs are normalized uv.y {uvs[i].y}."); + Assert.IsTrue(uvs[i].z <= 1.0f + tolerance, $"Expected that the output uvs are normalized uv.z {uvs[i].z}."); + Assert.IsTrue(uvs[i].z >= 0.0f - tolerance, $"Expected that the output uvs are normalized uv.z {uvs[i].z}."); + } + } + + public static Color[] GetRenderTextureData(RenderTexture rt) + { + var texture2D = new Texture2D(rt.width, rt.height, TextureFormat.RGBAFloat, false); + RenderTexture.active = rt; + texture2D.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); + texture2D.Apply(); + RenderTexture.active = null; + Color[] pixels = texture2D.GetPixels(); + return pixels; + } + } + + internal class UVMeshTests + { + [Test] + public void Build_SingleTriangleUVsAreNormalized_PositionsAreSetToUV0() + { + Mesh inputMesh = TestUtils.CreateSingleTriangleMesh(); + UVMesh uvMesh = new UVMesh(); + Assert.IsTrue(uvMesh.Build(inputMesh), "Building the uv mesh failed."); + Vector3[] outputPositions = uvMesh.Mesh.vertices; + Vector2[] inputUVs = inputMesh.uv; + Assert.AreEqual(inputMesh.vertices.Length, outputPositions.Length, $"Expected that the uv mesh has the same number of vertices as the input mesh."); + for (int i = 0; i < outputPositions.Length; ++i) + { + Vector3 inputUV = new Vector3(inputUVs[i].x, inputUVs[i].y, 0.0f); + Assert.AreEqual(inputUV, outputPositions[i], $"Expected that the input mesh uvs are in the output uv mesh positions."); + } + } + + // Array of UV transformations. Each element is a tuple of (scale, translate). + public static readonly Vector4[] UVSTs = + { + new(1.0f, 1.0f, 0.0f, 1.5f), + new(1.0f, 1.0f, 1.5f, 0.0f), + new(1.0f, 1.0f, 0.0f, -1.5f), + new(1.0f, 1.0f, -1.5f, 0.0f), + new(1.0f, 1.5f, 0.0f, 0.0f), + new(1.0f, 1.0f, 0.0f, 0.0f), + }; + + [Test] + public void Build_SingleTriangleWithNonNormalizedUVs_UVsAreNormalized([ValueSource(nameof(UVSTs))] Vector4 uvSTs) + { + Mesh inputMesh = TestUtils.CreateSingleTriangleMesh(); + TestUtils.ScaleUVs(new Vector2(uvSTs.x, uvSTs.y), ref inputMesh); + TestUtils.TranslateUVs(new Vector2(uvSTs.z, uvSTs.w), ref inputMesh); + UVMesh uvMesh = new UVMesh(); + Assert.IsTrue(uvMesh.Build(inputMesh), "Building the uv mesh failed."); + Vector3[] outputUVs = uvMesh.Mesh.vertices; + TestUtils.AssertThatUVsAreNormalized(outputUVs); + } + } + + internal class UVFallbackBufferResources : IDisposable + { + private readonly RayTracingBackend _backend; + internal RayTracingContext _context; + public CommandBuffer _cmd; + internal UVFallbackBufferBuilder _fallbackBufferBuilder; + internal ChartRasterizer _conservativeRasterizer; + + internal static RayTracingBackend BackendFromString(string backendAsString) + { + return Enum.Parse(backendAsString); + } + + public void Dispose() + { + _conservativeRasterizer?.Dispose(); + _fallbackBufferBuilder?.Dispose(); + _context?.Dispose(); + _cmd.Dispose(); + } + + internal UVFallbackBufferResources(RayTracingBackend backend) + { + _backend = backend; + var resources = new RayTracingResources(); + resources.Load(); + _context = new RayTracingContext(_backend, resources); + _fallbackBufferBuilder = new UVFallbackBufferBuilder(); + Material uvFbMaterial = new(Shader.Find("Hidden/UVFallbackBufferGeneration")); + _fallbackBufferBuilder.Prepare(uvFbMaterial); + _cmd = new CommandBuffer(); + ChartRasterizer.LoadShaders(out var software, out var hardware); + _conservativeRasterizer = new ChartRasterizer(software, hardware); + } + + internal static void BuildUVFallbackBuffer( + UVFallbackBufferResources resources, Mesh mesh, BuildFlags buildFlags, int width, int height, + out UVMesh uvMesh, out UVAccelerationStructure uvAS, out UVFallbackBuffer uvFB) + { + uvMesh = new UVMesh(); + Assert.IsTrue(uvMesh.Build(mesh), "Building the uv mesh failed."); + uvAS = new UVAccelerationStructure(); + uvAS.Build(resources._cmd, resources._context, uvMesh, buildFlags); + uvFB = new UVFallbackBuffer(); + + var indexCount = mesh.triangles.Length; + using var vertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, indexCount, UnsafeUtility.SizeOf()); + using var vertexToOriginalVertex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, indexCount, sizeof(uint)); + using var vertexToChartID = new GraphicsBuffer(GraphicsBuffer.Target.Structured, indexCount, sizeof(uint)); + var rasterizerBuffers = new ChartRasterizer.Buffers + { + vertex = vertex, + vertexToOriginalVertex = vertexToOriginalVertex, + vertexToChartID = vertexToChartID + }; + + Assert.IsTrue(uvFB.Build(resources._cmd, resources._fallbackBufferBuilder, width, height, uvMesh), "Building the uv fallback buffer failed."); + Graphics.ExecuteCommandBuffer(resources._cmd); + resources._cmd.Clear(); + } + + internal static void GetUVFallbackBuffer(UVFallbackBufferResources resources, Mesh mesh, BuildFlags buildFlags, int width, int height, out Color[] fallbackData) + { + BuildUVFallbackBuffer(resources, mesh, buildFlags, width, height, out UVMesh uvMesh, out UVAccelerationStructure uvAS, out UVFallbackBuffer uvFB); + fallbackData = TestUtils.GetRenderTextureData(uvFB.UVFallbackRT); + uvMesh.Dispose(); + uvAS.Dispose(); + uvFB.Dispose(); + } + + internal static void FallbackBufferStats(Color[] fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels) + { + numOccupiedPixels = 0; + numInvalidFallbackPixels = 0; + for (int i = 0; i < fallbackData.Length; ++i) + { + if (fallbackData[i].r < 0.0f) + { + ++numInvalidFallbackPixels; + } + else + { + ++numOccupiedPixels; + Assert.IsTrue(fallbackData[i].g >= 0.0f, "Expect that fallback v is not negative when u is not negative."); + } + } + } + } + + [Category("RequiresGPU")] + [Explicit("UVFallbackBufferTests requires a GPU to run as it uses conservative raster")] + [TestFixture("Compute")] + [TestFixture("Hardware")] + internal class UVFallbackBufferTests + { + private UVFallbackBufferResources _resources; + private readonly RayTracingBackend _backend; + + public UVFallbackBufferTests(string backendAsString) + { + _backend = UVFallbackBufferResources.BackendFromString(backendAsString); + } + + [SetUp] + public void SetUp() + { + if (!SystemInfo.supportsRayTracing && _backend == RayTracingBackend.Hardware) + { + Assert.Ignore("Cannot run test on this Graphics API. Hardware RayTracing is not supported"); + } + + if (!SystemInfo.supportsComputeShaders && _backend == RayTracingBackend.Compute) + { + Assert.Ignore("Cannot run test on this Graphics API. Compute shaders are not supported"); + } + + if (SystemInfo.graphicsDeviceName.Contains("llvmpipe")) + { + Assert.Ignore("Cannot run test on this device (Renderer: llvmpipe (LLVM 10.0.0, 128 bits)). Tests are disabled because they fail on some platforms (that do not support 11 SSBOs). Once we do not run Ubuntu 18.04 try removing this"); + } + + _resources = new UVFallbackBufferResources(_backend); + } + + [TearDown] + public void TearDown() + { + _resources?.Dispose(); + } + + private static void OccupancyBufferStats(Color[] occupancyData, out int numOccupiedPixels) + { + numOccupiedPixels = 0; + for (int i = 0; i < occupancyData.Length; ++i) + if (Mathf.Approximately(occupancyData[i].r, 1.0f)) + ++numOccupiedPixels; + } + + private static BuildFlags[] _bvhBuildFlags = { BuildFlags.PreferFastBuild, BuildFlags.MinimizeMemory, BuildFlags.None }; + + [Test] + public void Build_SingleTriangle3x3_GetCorrectFallbackBuffer([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + int width = 3; + int height = 3; + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out Color[] fallbackData); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + + var support = SystemInfo.supportsConservativeRaster ? "supports" : "does not support"; + var useConservativeRaster = $"Platform {support} conservative raster."; + Assert.AreEqual(6, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer. {useConservativeRaster}"); + Assert.AreEqual(3, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + Assert.AreEqual(width * height, numOccupiedPixels + numInvalidFallbackPixels, "The sum of occupied and invalid pixels in the uv fallback buffer should match the number of pixels in the buffer."); + } + + [Test] + public void Build_SingleTriangle4x3_GetCorrectFallbackBuffer([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + int width = 4; + int height = 3; + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + mesh.uv = new Vector2[] + { + new(0, 1), + new((float)width/(float)height, 1), + new(0, 0) + }; + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out Color[] fallbackData); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + + var support = SystemInfo.supportsConservativeRaster ? "supports" : "does not support"; + var useConservativeRaster = $"Platform {support} conservative raster."; + Assert.AreEqual(9, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer. {useConservativeRaster}"); + Assert.AreEqual(3, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + Assert.AreEqual(width * height, numOccupiedPixels + numInvalidFallbackPixels, "The sum of occupied and invalid pixels in the uv fallback buffer should match the number of pixels in the buffer."); + } + + private static int[] _multipassTexelSampleCounts = { 1, 16, 32, 127, 128 }; + [Test] + public void Build_SingleTriangle4x3UsingTexelMultipass_GetCorrectFallbackBuffer([ValueSource(nameof(_multipassTexelSampleCounts))] int samplesPerTexel) + { + int width = 4; + int height = 3; + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + mesh.uv = new Vector2[] + { + new(0, 1), + new((float)width/(float)height, 1), + new(0, 0) + }; + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, BuildFlags.None, width, height, out Color[] fallbackData); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + + var support = SystemInfo.supportsConservativeRaster ? "supports" : "does not support"; + var useConservativeRaster = $"Platform {support} conservative raster."; + Assert.AreEqual(9, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer. {useConservativeRaster}"); + Assert.AreEqual(3, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + Assert.AreEqual(width * height, numOccupiedPixels + numInvalidFallbackPixels, "The sum of occupied and invalid pixels in the uv fallback buffer should match the number of pixels in the buffer."); + } + + private static int[] _multipassMaxSampleCounts = { 1, 10, 100, 1000, 10000 }; + [Test] + public void Build_SingleTriangle40x30UsingMaxSamples_GetCorrectFallbackBuffer([ValueSource(nameof(_multipassMaxSampleCounts))] int samplesPerPass) + { + int width = 40; + int height = 30; + Mesh mesh = TestUtils.CreateSingleTriangleMesh(); + mesh.uv = new Vector2[] + { + new(0, 1), + new((float)width/(float)height, 1), + new(0, 0) + }; + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, BuildFlags.None, width, height, out Color[] fallbackData); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + + Assert.That(numOccupiedPixels, Is.InRange(628, 632), $"Unexpected number of occupied pixels in the uv fallback buffer."); + Assert.That(numInvalidFallbackPixels, Is.InRange(568, 572), "Unexpected number of invalid pixels in the uv fallback buffer."); + Assert.AreEqual(width * height, numOccupiedPixels + numInvalidFallbackPixels, "The sum of occupied and invalid pixels in the uv fallback buffer should match the number of pixels in the buffer."); + } + + [Test] + public void Build_Quad2x2_AllFallbackTexelsHit([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + + int width = 2; + int height = 2; + Mesh mesh = TestUtils.CreateQuadMesh(); + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out Color[] fallbackData); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + + Assert.AreEqual(4, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer."); + Assert.AreEqual(0, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + } + [Test] + + public void Build_QuadWithNonSquareUVs_AllCoveredTexelsAreOccupied([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + int width = 2; + int height = 2; + Mesh mesh = TestUtils.CreateQuadMesh(); + mesh.uv = new[] + { + new Vector2(0, 0), + new Vector2(0.501f, 0), // Cover the 2 rightmost texels by only a tiny amount + new Vector2(0, 1), + new Vector2(0.501f, 1) + }; + + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out Color[] fallbackData); + + Assert.Greater(fallbackData[0 + 0 * width].r, 0.0f, "Pixel at (0, 0) should be a hit"); + Assert.Greater(fallbackData[1 + 0 * width].r, 0.0f, "Pixel at (1, 0) should be a hit"); + Assert.Greater(fallbackData[0 + 1 * width].r, 0.0f, "Pixel at (0, 1) should be a hit"); + Assert.Greater(fallbackData[1 + 1 * width].r, 0.0f, "Pixel at (1, 1) should be a hit"); + } + + private static (int, int)[] _squareBufferResolutions = { (1, 1), (4, 4) }; + [Test] + public void Build_QuadSquareUVsToSquareUVFallbackBuffer_AllTexelsHit([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags, [ValueSource(nameof(_squareBufferResolutions))] (int width, int height) resolution) + { + Mesh mesh = TestUtils.CreateQuadMesh(); + mesh.uv = new Vector2[] + { + new(0, 0), + new(0.55f, 0), + new(0, 0.55f), + new(0.55f, 0.55f) + }; + + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, resolution.width, resolution.height, out Color[] fallbackData); + + int i = 0; + foreach (var color in fallbackData) + { + Assert.Greater(color.r, 0.0f, $"Texel {i} should get a hit as the UVFallbackBuffer should be fully occupied."); + i++; + } + } + + private static (int, int)[] _nonSquareBufferResolutions = { (1, 2), (2, 1), (4, 8), (8, 4) }; + [Test] + public void Build_QuadSquareUVsToNonSquareUVFallbackBuffer_TexelsInSquareHit([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags, [ValueSource(nameof(_nonSquareBufferResolutions))] (int width, int height) resolution) + { + Mesh mesh = TestUtils.CreateQuadMesh(); + mesh.uv = new[] + { + new Vector2(0, 0), + new Vector2(0.55f, 0), + new Vector2(0, 0.55f), + new Vector2(0.55f, 0.55f) + }; + + UVFallbackBufferResources.GetUVFallbackBuffer(_resources, mesh, buildFlags, resolution.width, resolution.height, out Color[] fallbackData); + + int uvBoundsResolution = Mathf.Min(resolution.width, resolution.height); + for (int y = 0; y < resolution.height; ++y) + { + for (int x = 0; x < resolution.width; ++x) + { + var color = fallbackData[x + y * resolution.width]; + if (x < uvBoundsResolution && y < uvBoundsResolution) + Assert.Greater(color.r, 0.0f, $"Texel [{x}, {y}] should get a hit as the location falls within the UV bounds."); + else + Assert.AreEqual(-1.0f, color.r, 0.0001f, $"Texel [{x}, {y}] should get a miss as the location falls outside the UV bounds."); + } + } + } + } + + [Category("RequiresGPU")] + [Explicit("UVSamplingTests requires a GPU to run as it uses conservative raster")] + [TestFixture("Compute")] + [TestFixture("Hardware")] + internal class UVSamplingTests + { + private UVFallbackBufferResources _resources; + private SamplingResources _samplingResources; + private readonly RayTracingBackend _backend; + private IRayTracingShader _gBufferShader; + + public UVSamplingTests(string backendAsString) + { + _backend = UVFallbackBufferResources.BackendFromString(backendAsString); + } + + [SetUp] + public void SetUp() + { + if (!SystemInfo.supportsRayTracing && _backend == RayTracingBackend.Hardware) + { + Assert.Ignore("Cannot run test on this Graphics API. Hardware RayTracing is not supported"); + } + + if (!SystemInfo.supportsComputeShaders && _backend == RayTracingBackend.Compute) + { + Assert.Ignore("Cannot run test on this Graphics API. Compute shaders are not supported"); + } + + if (SystemInfo.graphicsDeviceName.Contains("llvmpipe")) + { + Assert.Ignore("Cannot run test on this device (Renderer: llvmpipe (LLVM 10.0.0, 128 bits)). Tests are disabled because they fail on some platforms (that do not support 11 SSBOs). Once we do not run Ubuntu 18.04 try removing this"); + } + + _resources = new UVFallbackBufferResources(_backend); + _gBufferShader = _resources._context.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/LightmapGBufferIntegration.urtshader"); + _samplingResources = new SamplingResources(); + _samplingResources.Load((uint)SamplingResources.ResourceType.All); + } + + [TearDown] + public void TearDown() + { + _resources?.Dispose(); + _samplingResources?.Dispose(); + } + + private static BuildFlags[] _bvhBuildFlags = { BuildFlags.PreferFastBuild, BuildFlags.MinimizeMemory, BuildFlags.None }; + + public struct HitEntry + { + public uint instanceID; + public uint primitiveIndex; + public Unity.Mathematics.float2 barycentrics; + }; + + private static void GetHitEntries(ref HitEntry[] hitEntries, AsyncGPUReadbackRequest request) + { + Debug.Assert(!request.hasError); + if (!request.hasError) + { + var src = request.GetData(); + hitEntries = new HitEntry[src.Length]; + for (int i = 0; i < src.Length; ++i) + { + hitEntries[i] = src[i]; + } + return; + } + hitEntries = null; + } + + public static HitEntry[] ReadBackHitEntries(CommandBuffer cmd, GraphicsBuffer gBuffer) + { + HitEntry[] hitEntries = null; + Debug.Assert(gBuffer.stride == 16); + using GraphicsBuffer stagingBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopyDestination, gBuffer.count, gBuffer.stride); + cmd.CopyBuffer(gBuffer, stagingBuffer); + cmd.RequestAsyncReadback(stagingBuffer, (AsyncGPUReadbackRequest request) => { GetHitEntries(ref hitEntries, request); }); + cmd.WaitAllAsyncReadbackRequests(); + Graphics.ExecuteCommandBuffer(cmd); + cmd.Clear(); + return hitEntries; + } + + static private HitEntry[] GetSampleHitEntries(CommandBuffer cmd, IRayTracingShader gbufferShader, UVMesh uvMesh, UVAccelerationStructure uvAS, UVFallbackBuffer uvFB, SamplingResources samplingResources, Vector2Int instanceTexelOffset, int sampleCount, bool stochasticAntialiasing = true, uint superSamplingWidth = 1) + { + uint chunkSize = (uint)uvFB.Width * (uint)uvFB.Height; + uint expandedSampleWidth = math.ceilpow2((uint)sampleCount); + uint expandedSize = chunkSize * expandedSampleWidth; + Debug.Assert(expandedSize <= 524288, "The expanded size quite large, consider splitting into multiple dispatches."); + + // Set up compacted GBuffer - here we don't do compaction but we simply inject all the texels - an identity compaction mapping if you will + using GraphicsBuffer compactedTexelIndices = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)(chunkSize), sizeof(uint)); + using GraphicsBuffer compactedGBufferLength = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, 1, sizeof(uint)); + compactedGBufferLength.SetData(new uint[] { chunkSize }); + uint[] texelIndices = new uint[chunkSize]; + for (uint i = 0; i < chunkSize; ++i) + texelIndices[i] = i; + compactedTexelIndices.SetData(texelIndices); + + // Set up scratch buffer + GraphicsBuffer traceScratchBuffer = null; + var requiredSizeInBytes = gbufferShader.GetTraceScratchBufferRequiredSizeInBytes(expandedSize, 1, 1); + if (requiredSizeInBytes > 0) + traceScratchBuffer = new GraphicsBuffer(RayTracingHelper.ScratchBufferTarget, (int)(requiredSizeInBytes / 4), 4); + + // Run the GBuffer shader + using GraphicsBuffer gBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.CopySource, (int)expandedSize, UnsafeUtility.SizeOf()); + ExpansionHelpers.GenerateGBuffer( + cmd, + gbufferShader, + gBuffer, + traceScratchBuffer, + samplingResources, + uvAS, + uvFB, + compactedGBufferLength, + compactedTexelIndices, + instanceTexelOffset, + 0, + chunkSize, + expandedSampleWidth, + (uint)sampleCount, + 0, + stochasticAntialiasing ? AntiAliasingType.Stochastic : AntiAliasingType.SuperSampling, + superSamplingWidth); + + List hitEntries = new(); + HitEntry[] hits = ReadBackHitEntries(cmd, gBuffer); + + for (uint i = 0; i < hits.Length; ++i) + { + var hit = hits[i]; + if (expandedSampleWidth > sampleCount) + { + // The expanded sample width is larger than the sample count, so we need to filter out the unused samples + // The local sample index is the texel index modulo the expanded sample width + uint localSampleIndex = i % expandedSampleWidth; + // We only want to keep the samples that are less than the original sample count + if (localSampleIndex < (uint)sampleCount) + continue; + } + hitEntries.Add(hit); + } + + traceScratchBuffer?.Release(); + return hitEntries.ToArray(); + } + + static private HitEntry[] FilterHitEntries(uint instanceID, uint primitiveIndex, HitEntry[] allEntries) + { + List hitEntries = new(); + foreach (var entry in allEntries) + { + if (entry.instanceID == instanceID && entry.primitiveIndex == primitiveIndex) + hitEntries.Add(entry); + } + return hitEntries.ToArray(); + } + + [Test] + public void Sample_SingleQuadIn3x3_BothPrimitivesAreHit([ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + Vector2Int instanceTexelOffset = new Vector2Int(0, 0); + int width = 3; + int height = 3; + + Mesh mesh = TestUtils.CreateQuadMesh(); + UVMesh uvMesh = null; + UVAccelerationStructure uvAS = null; + UVFallbackBuffer uvFB = null; + try + { + UVFallbackBufferResources.BuildUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out uvMesh, out uvAS, out uvFB); + { + Color[] fallbackData = TestUtils.GetRenderTextureData(uvFB.UVFallbackRT); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + Assert.AreEqual(width * height, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer."); + Assert.AreEqual(0, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + } + + HitEntry[] hits = GetSampleHitEntries(_resources._cmd, _gBufferShader, uvMesh, uvAS, uvFB, _samplingResources, instanceTexelOffset, 1); + Assert.IsTrue(FilterHitEntries(0, 0, hits).Length >= 3, "Expected that triangle 0 intersects at least 3 texels."); + Assert.IsTrue(FilterHitEntries(0, 1, hits).Length >= 3, "Expected that triangle 1 intersects at least 3 texels."); + } + finally + { + uvMesh?.Dispose(); + uvAS?.Dispose(); + uvFB?.Dispose(); + } + } + + private static float Discrepancy(float2[] unitSamples, uint iterations, out float2 minDiscrepancyBox, out float2 maxDiscrepancyBox) + { + float minDiscrepancy = float.MaxValue; + float maxDiscrepancy = float.MinValue; + minDiscrepancyBox = 0.0f; + maxDiscrepancyBox = 0.0f; + System.Random rand = new(1345236); + int numPoints = unitSamples.Length; + for (int i = 0; i < iterations; ++i) + { + // make a new random box + float2 randomBox = new float2((float)rand.NextDouble(), (float)rand.NextDouble()); + float boxVolume = randomBox.x * randomBox.y; + // find points in box + int pointsInBox = 0; + for (int p = 0; p < unitSamples.Length; ++p) + { + if (unitSamples[p].x <= randomBox.x && unitSamples[p].y <= randomBox.y) + pointsInBox++; + } + float discrepancyValue = Math.Abs(((float)pointsInBox / (float)numPoints) - boxVolume); + if (discrepancyValue > maxDiscrepancy) + { + maxDiscrepancy = discrepancyValue; + maxDiscrepancyBox = randomBox; + } + if (discrepancyValue < minDiscrepancy) + { + minDiscrepancy = discrepancyValue; + minDiscrepancyBox = randomBox; + } + } + return maxDiscrepancy; + } + + private static readonly (int width, int height, int sampleCount, float expectedDiscrepancy)[] SamplingVariations = +{ + (1, 1, 128, 0.026f), + (4, 4, 16, 0.020f), + (16, 16, 1, 0.034f) + }; + + float2[] ComputeHitUvs(Mesh mesh, HitEntry[] hits) + { + var indices = mesh.triangles; + var uvs = new List(); + mesh.GetUVs(0, uvs); + + var res = new float2[hits.Length]; + for (int i = 0; i < hits.Length; ++i) + { + float2 p0 = uvs[indices[3 * hits[i].primitiveIndex]]; + float2 p1 = uvs[indices[3 * hits[i].primitiveIndex + 1]]; + float2 p2 = uvs[indices[3 * hits[i].primitiveIndex + 2]]; + res[i] = p0 * (1.0f - hits[i].barycentrics.x - hits[i].barycentrics.y) + p1 * hits[i].barycentrics.x + p2 * hits[i].barycentrics.y; + } + + return res; + } + + Bounds ComputeUvBounds(float2[] uvs) + { + Bounds res = new Bounds(new float3(uvs[0], 0.0f), float3.zero); + foreach (var uv in uvs) + res.Encapsulate(new float3(uv, 0.0f)); + + return res; + } + + [Test] + public void Sample_SingleQuadIn_SamplesAreDistributed([ValueSource(nameof(SamplingVariations))] (int width, int height, int uvSampleCount, float expectedDiscrepancy) testData, [ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + (int width, int height, int uvSampleCount, float expectedDiscrepancy) = testData; + + Vector2Int instanceTexelOffset = new Vector2Int(0, 0); + Mesh mesh = TestUtils.CreateQuadMesh(); + + UVFallbackBufferResources.BuildUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out UVMesh uvMesh, out UVAccelerationStructure uvAS, out UVFallbackBuffer uvFB); + { + Color[] fallbackData = TestUtils.GetRenderTextureData(uvFB.UVFallbackRT); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + Assert.AreEqual(width * height, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer."); + Assert.AreEqual(0, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + } + + HitEntry[] hits = GetSampleHitEntries(_resources._cmd, _gBufferShader, uvMesh, uvAS, uvFB, _samplingResources, instanceTexelOffset, uvSampleCount); + Assert.IsTrue(hits.Length == uvSampleCount * width * height, "Expected that every sample produces a hit."); + + var hitUvs = ComputeHitUvs(mesh, hits); + Bounds pointBounds = ComputeUvBounds(hitUvs); + + Assert.IsTrue(pointBounds.size.x <= 1.0f, $"Expected samples fall in [0;1] - x size {pointBounds.size.x} too large."); + Assert.IsTrue(pointBounds.size.y <= 1.0f, $"Expected samples fall in [0;1] - y size {pointBounds.size.y} too large."); + Assert.IsTrue(pointBounds.min.x >= 0.0f, $"Expected samples fall in [0;1] - min x {pointBounds.min.x} is negative."); + Assert.IsTrue(pointBounds.min.y >= 0.0f, $"Expected samples fall in [0;1] - min y {pointBounds.min.y} is negative."); + Assert.IsTrue(pointBounds.max.x <= 1.0f, $"Expected samples fall in [0;1] - max x {pointBounds.max.x} is greater than 1."); + Assert.IsTrue(pointBounds.max.y <= 1.0f, $"Expected samples fall in [0;1] - max y {pointBounds.max.y} is greater than 1."); + // Empirical bound for this configuration of points in the Sobol sequence, could change for another quasi random sequence + float minSize = 0.98f; + Assert.IsTrue(pointBounds.size.x >= minSize, $"Expected that samples fall in [0;{minSize}] - bounds width is {pointBounds.size.x}, while {minSize} was expected."); + Assert.IsTrue(pointBounds.size.y >= minSize, $"Expected that samples fall in [0;{minSize}] - bounds height is {pointBounds.size.y}, while {minSize} was expected."); + + + float discrepancy = Discrepancy(hitUvs, 6000, out float2 minDiscrepancyBox, out float2 maxDiscrepancyBox); + + Assert.IsTrue(discrepancy < expectedDiscrepancy, $"Expected discrepancy measure to be less than {expectedDiscrepancy} was {discrepancy}."); +#if false + string uvsMessage = new($"BN {width} x {height} - {uvSampleCount} samples - discrepancy: {discrepancy}\n"); + foreach (var uv in uvSamples) + { + uvsMessage += $"{uv.x}\t{uv.y}\n"; + } + Console.WriteLine(uvsMessage); +#endif + + uvMesh?.Dispose(); + uvAS?.Dispose(); + uvFB?.Dispose(); + } + + private static readonly (int width, int height, int sampleCount, uint superSamplingWidth, Vector3 expectedBoundsSize, Vector3 expectedBoundsOrigin, float expectedDiscrepancy)[] SuperSamplingVariations = + { + (1, 1, 32, 1, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.5f, 0.5f, 0), 0.74f), + (1, 1, 32, 2, new Vector3(0.5f, 0.5f, 0.0f), new Vector3(0.25f, 0.25f, 0), 0.42f), + (1, 1, 32, 4, new Vector3(0.75f, 0.75f, 0.0f), new Vector3(0.125f, 0.125f, 0), 0.23f), + (1, 1, 64, 8, new Vector3(0.875f, 0.875f, 0.0f), new Vector3(0.0625f, 0.0625f, 0), 0.12f), + (2, 2, 32, 2, new Vector3(0.75f, 0.75f, 0.0f), new Vector3(0.125f, 0.125f, 0), 0.23f), + }; + + [Test] + public void Sample_SingleQuad_SuperSamplesAreCorrect([ValueSource(nameof(SuperSamplingVariations))] (int width, int height, int uvSampleCount, uint superSamplingWidth, Vector3 expectedBoundsSize, Vector3 expectedBoundsOrigin, float expectedDiscrepancy) testData, [ValueSource(nameof(_bvhBuildFlags))] BuildFlags buildFlags) + { + (int width, int height, int uvSampleCount, uint superSamplingWidth, Vector3 expectedBoundsSize, Vector3 expectedBoundsOrigin, float expectedDiscrepancy) = testData; + + Vector2Int instanceTexelOffset = new Vector2Int(0, 0); + Mesh mesh = TestUtils.CreateQuadMesh(); + + UVFallbackBufferResources.BuildUVFallbackBuffer(_resources, mesh, buildFlags, width, height, out UVMesh uvMesh, out UVAccelerationStructure uvAS, out UVFallbackBuffer uvFB); + { + Color[] fallbackData = TestUtils.GetRenderTextureData(uvFB.UVFallbackRT); + UVFallbackBufferResources.FallbackBufferStats(fallbackData, out int numOccupiedPixels, out int numInvalidFallbackPixels); + Assert.AreEqual(width * height, numOccupiedPixels, $"Unexpected number of occupied pixels in the uv fallback buffer."); + Assert.AreEqual(0, numInvalidFallbackPixels, "Unexpected number of invalid pixels in the uv fallback buffer."); + } + + bool useRandomSuperSampling = false; + HitEntry[] hits = GetSampleHitEntries(_resources._cmd, _gBufferShader, uvMesh, uvAS, uvFB, _samplingResources, instanceTexelOffset, uvSampleCount, useRandomSuperSampling, superSamplingWidth); + Assert.IsTrue(hits.Length == uvSampleCount * width * height, "Expected that every sample produces a hit."); + + + var hitUvs = ComputeHitUvs(mesh, hits); + Bounds pointBounds = ComputeUvBounds(hitUvs); + + // check that the bounds matches + Assert.AreEqual(expectedBoundsSize.x, pointBounds.size.x, 0.001f, "Expected bounds size x is incorrect."); + Assert.AreEqual(expectedBoundsSize.y, pointBounds.size.y, 0.001f, "Expected bounds size y is incorrect."); + Assert.AreEqual(expectedBoundsOrigin.x, pointBounds.min.x, 0.001f, "Expected bounds origin x is incorrect."); + Assert.AreEqual(expectedBoundsOrigin.y, pointBounds.min.y, 0.001f, "Expected bounds origin y is incorrect."); + + // check that the discrepancy is as expected + float discrepancy = Discrepancy(hitUvs, 6000, out float2 minDiscrepancyBox, out float2 maxDiscrepancyBox); + Assert.AreEqual(expectedDiscrepancy, discrepancy, 0.01f, $"Expected discrepancy measure is incorrect."); + + uvMesh?.Dispose(); + uvAS?.Dispose(); + uvFB?.Dispose(); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs.meta new file mode 100644 index 00000000000..c55aa3fb1db --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/UVFallbackBufferTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb3146042bceada45a54e9cd824a21d5 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef new file mode 100644 index 00000000000..ca0f2e24a4d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Unity.PathTracing.Editor.Tests", + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:df380645f10b7bc4b97d4f5eb6303d95", + "GUID:214c0945bb158c940aada223f3223ee8", + "GUID:c49c619b6af2be941af9bcbca2641964" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/Editor/Converter/PPv2/PPv2ToURPConvertersDefinition.asmdef.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef.meta similarity index 76% rename from Packages/com.unity.render-pipelines.universal/Editor/Converter/PPv2/PPv2ToURPConvertersDefinition.asmdef.meta rename to Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef.meta index 8427bfd0d7d..41be3f66b8c 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/Converter/PPv2/PPv2ToURPConvertersDefinition.asmdef.meta +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3fa284cf2189b184e8d506b83da68377 +guid: ec4944adf00f4b1478ae164e5eac5b80 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj new file mode 100644 index 00000000000..8f84f09bd45 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj @@ -0,0 +1,23 @@ + + + + false + true + Unity.PathTracing.Editor.Tests + + true + + + Editor + + + + + + + + + + + + \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj.meta new file mode 100644 index 00000000000..b87b9c43196 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/Unity.PathTracing.Editor.Tests.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ee9095c25e614d59bcb015b49fdba96d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs new file mode 100644 index 00000000000..b6a9027b5e6 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs @@ -0,0 +1,323 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Unity.Mathematics; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; +using RayTracingBackend = UnityEngine.Rendering.UnifiedRayTracing.RayTracingBackend; + +namespace UnityEngine.PathTracing.Tests +{ + using InstanceHandle = Handle; + using MaterialHandle = Handle; + + [TestFixture("Compute")] + [TestFixture("Hardware")] + internal class WorldTests + { + private readonly RayTracingBackend _backend; + private World _world; + private bool _respectLightLayers; + private bool _autoEstimateLUTRange; + private GraphicsBuffer _buildScratchBuffer; + private RayTracingContext _rayTracingContext; + private Material _defaultMaterial; + private CommandBuffer _cmd; + private SamplingResources _samplingResources; + + public WorldTests(string backendAsString) + { + _backend = Enum.Parse(backendAsString); + } + + [SetUp] + public void Setup() + { + if (!SystemInfo.supportsRayTracing && _backend == RayTracingBackend.Hardware) + Assert.Ignore("Cannot run test on this Graphics API. Hardware RayTracing is not supported"); + + var resources = new RayTracingResources(); + resources.Load(); + _rayTracingContext = new RayTracingContext(_backend, resources); + _defaultMaterial = UnityEditor.AssetDatabase.GetBuiltinExtraResource("Default-Material.mat"); + _respectLightLayers = false; + _autoEstimateLUTRange = false; + _cmd = new CommandBuffer(); + _cmd.name = "PathTracing.Tests Command Buffer"; + _world = new World(); + var worldResources = new WorldResourceSet(); + worldResources.LoadFromAssetDatabase(); + _world.Init(_rayTracingContext, worldResources); + _world.AddMaterial(MaterialPool.ConvertUnityMaterialToMaterialDescriptor(_defaultMaterial), UVChannel.UV0); + + _samplingResources = new SamplingResources(); + _samplingResources.Load((uint)SamplingResources.ResourceType.All); + } + + [TearDown] + public void TearDown() + { + _world?.Dispose(); + _buildScratchBuffer?.Dispose(); + _rayTracingContext?.Dispose(); + _cmd?.Dispose(); + + _samplingResources?.Dispose(); + // Null this buffer so nobody attempts to access properties of a disposed buffer + _buildScratchBuffer = null; + } + + private static World.LightDescriptor[] CreateLights(int lightCount) + { + var lights = new Light[lightCount]; + for (int i = 0; i < lightCount; ++i) + { + var lightGameObject = new GameObject($"Test Light {i}"); + var light = lightGameObject.AddComponent(); + lights[i] = light; + } + return Util.ConvertUnityLightsToLightDescriptors(lights, false); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void World_AddLight_IncreaseLightCount(int lightCount) + { + #if UNITY_EDITOR_OSX + if (System.Runtime.InteropServices.RuntimeInformation.OSArchitecture != System.Runtime.InteropServices.Architecture.Arm64) + NUnit.Framework.Assert.Ignore("Fails on MacOS13 UUM-111386"); + #endif + var lights = CreateLights(lightCount); + _world.AddLights(lights, _respectLightLayers, _autoEstimateLUTRange, MixedLightingMode.IndirectOnly); + _world.lightPickingMethod = LightPickingMethod.LightGrid; + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, false); + Graphics.ExecuteCommandBuffer(_cmd); + Assert.AreEqual(lightCount, _world.LightCount); + } + + [Test] + public void World_AddAndRemoveLight_CountUpdates() + { + Assert.AreEqual(0, _world.LightCount); + var light = CreateLights(1); + var handles = _world.AddLights(light, _respectLightLayers, _autoEstimateLUTRange, MixedLightingMode.IndirectOnly); + Assert.AreEqual(1, _world.LightCount); + _world.RemoveLights(handles); + Assert.AreEqual(0, _world.LightCount); + } + + static Bounds CalculateBounds(Mesh mesh, Matrix4x4 localToWorld) + { + Bounds bounds = new Bounds(localToWorld.GetPosition(), Vector3.zero); + var verts = mesh.vertices; + foreach (var vert in verts) + { + var worldPos = localToWorld.MultiplyPoint(vert); + bounds.Encapsulate(worldPos); + } + return bounds; + } + + InstanceHandle AddInstanceToWorld(Mesh mesh, Matrix4x4 localToWorld, Material material) + { + Bounds bounds = CalculateBounds(mesh, localToWorld); + const bool isStatic = true; + uint objectLayerMask = 1; + bool enableEmissiveSampling = true; + var materialHandle = _world.AddMaterial(MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material), UVChannel.UV0); + var mask = World.GetInstanceMask(ShadowCastingMode.On, isStatic, RenderedGameObjectsFilter.OnlyStatic); + + return _world.AddInstance(mesh, new MaterialHandle[] { materialHandle }, new uint[] { mask }, objectLayerMask, localToWorld, bounds, isStatic, RenderedGameObjectsFilter.OnlyStatic, enableEmissiveSampling); + } + + static AccelStructInstances GetAccelStructInstancesFromWorld(World world) + { + return world.GetAccelerationStructure().Instances; + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void World_AddInstance_IncreasesInstanceCount(int instanceCount) + { + Mesh mesh = Resources.GetBuiltinResource("Cube.fbx"); + for (int i = 0; i < instanceCount; i++) + { + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one * i); + AddInstanceToWorld(mesh, localToWorld, _defaultMaterial); + } + + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, false); + Graphics.ExecuteCommandBuffer(_cmd); + + var instances = GetAccelStructInstancesFromWorld(_world); + Assert.AreEqual(instanceCount, instances.GetInstanceCount()); + } + + // Struct for reading geometry pool data back to CPU + struct VertexData + { + public Vector3 Position; + public float2 UV0; + public float2 UV1; + public float OctahedralNormal; + } + + [Test] + public void World_AddInstances_UploadedVertexDataMatches() + { + // Add some meshes to world + Mesh[] meshes = new[] { + Resources.GetBuiltinResource("Cube.fbx"), + Resources.GetBuiltinResource("Sphere.fbx") + }; + for (int i = 0; i < meshes.Length; i++) + { + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one * i); + AddInstanceToWorld(meshes[i], localToWorld, _defaultMaterial); + } + + // Build world + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, false); + Graphics.ExecuteCommandBuffer(_cmd); + + // Readback vertex buffer data from geo pool + var instances = GetAccelStructInstancesFromWorld(_world); + int totalVertexCount = meshes.Sum(x => x.vertexCount); + var geoPoolVertices = new VertexData[totalVertexCount]; + instances.vertexBuffer.GetData(geoPoolVertices); + + // Check that data matches source + int vertexOffset = 0; + for (int i = 0; i < meshes.Length; i++) + { + var meshVerts = meshes[i].vertices; + var meshUVs = meshes[i].uv; + + for (int j = 0; j < meshVerts.Length; j++) + { + Assert.AreEqual(meshVerts[j], geoPoolVertices[vertexOffset + j].Position, $"Vertex positions at index {vertexOffset + j} didn't match"); + Assert.AreEqual(new float2(meshUVs[j].x, meshUVs[j].y), geoPoolVertices[vertexOffset + j].UV0, $"UVs at index {vertexOffset + j} didn't match"); + } + + vertexOffset += meshVerts.Length; + } + } + + [Test] + public void World_AddAndRemoveInstances_CountIsCorrect() + { + // Add 3 instances + var mesh = Resources.GetBuiltinResource("Cube.fbx"); + var handles = new InstanceHandle[3]; + for (int i = 0; i < 3; i++) + { + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one * i); + handles[i] = AddInstanceToWorld(mesh, localToWorld, _defaultMaterial); + } + + // Remove 1 instance + _world.RemoveInstance(handles[0]); + + // Build world + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, false); + Graphics.ExecuteCommandBuffer(_cmd); + + // Count should be 2 + var instances = GetAccelStructInstancesFromWorld(_world); + Assert.AreEqual(2, instances.GetInstanceCount()); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void World_AddInstance_IncreasesMeshLightCountIfEmissive(bool isEmissive) + { + // Make material with emission + Material material = new Material(_defaultMaterial); + if (isEmissive) + { + material.EnableKeyword("_EMISSION"); + material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; + material.SetColor("_EmissionColor", Color.green); + } + + // Add instance + var mesh = Resources.GetBuiltinResource("Cube.fbx"); + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one); + AddInstanceToWorld(mesh, localToWorld, material); + + // Build world + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, true); + Graphics.ExecuteCommandBuffer(_cmd); + + // Check that mesh light count increased + Assert.AreEqual(isEmissive ? 1 : 0, _world.MeshLightCount); + + CoreUtils.Destroy(material); + } + + [Test] + public void World_AddAndRemoveEmissiveInstance_MeshLightCountIsZero() + { + // Make material with emission + Material material = new Material(_defaultMaterial); + material.EnableKeyword("_EMISSION"); + material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; + material.SetColor("_EmissionColor", Color.green); + + // Add instance + var mesh = Resources.GetBuiltinResource("Cube.fbx"); + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one); + InstanceHandle instance = AddInstanceToWorld(mesh, localToWorld, material); + + _world.RemoveInstance(instance); + + // Build world + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, true); + Graphics.ExecuteCommandBuffer(_cmd); + + // Check that mesh light count is 0 + Assert.AreEqual(0, _world.MeshLightCount); + + CoreUtils.Destroy(material); + } + + [Test] + public void World_UpdateInstanceTransform_UploadsCorrectData() + { +#if UNITY_EDITOR_OSX + if (System.Runtime.InteropServices.RuntimeInformation.OSArchitecture != System.Runtime.InteropServices.Architecture.Arm64) + NUnit.Framework.Assert.Ignore("Fails on MacOS13 UUM-111386"); +#endif + // Add instance + var mesh = Resources.GetBuiltinResource("Cube.fbx"); + Matrix4x4 localToWorld = Matrix4x4.Translate(Vector3.one); + InstanceHandle instance = AddInstanceToWorld(mesh, localToWorld, _defaultMaterial); + + // Update its transform + Matrix4x4 newLocalToWorld = Matrix4x4.Translate(Vector3.one * 5); + Assert.AreNotEqual(localToWorld, newLocalToWorld); + _world.UpdateInstanceTransform(instance, newLocalToWorld); + + // Build world + _world.Build(new Bounds(), _cmd, ref _buildScratchBuffer, _samplingResources, false); + Graphics.ExecuteCommandBuffer(_cmd); + + // Readback instance buffer + var instances = GetAccelStructInstancesFromWorld(_world); + var instanceBuffer = instances.instanceBuffer.GetGpuBuffer(_cmd); + Graphics.ExecuteCommandBuffer(_cmd); + + // Check that transform was updated + var instancesArray = new AccelStructInstances.RTInstance[1]; + instanceBuffer.GetData(instancesArray); + Assert.AreEqual((float4x4)newLocalToWorld, instancesArray[0].localToWorld); + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs.meta new file mode 100644 index 00000000000..03a076a4f64 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/PathTracing/WorldTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cea18228eceb46f8baefec13bb6aad59 +timeCreated: 1696318367 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/RenderGraphTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/RenderGraphTests.cs index 1172c4156a2..6422e619858 100644 --- a/Packages/com.unity.render-pipelines.core/Tests/Editor/RenderGraphTests.cs +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/RenderGraphTests.cs @@ -6,6 +6,7 @@ using UnityEngine.TestTools; using Unity.Collections; using UnityEngine.Rendering.RendererUtils; +using System.Text.RegularExpressions; #if UNITY_EDITOR using UnityEditor; @@ -948,7 +949,7 @@ public void RenderGraphTilePropertiesWorksWithDepthOnlyReadFlag() { const int kWidth = 4; const int kHeight = 4; - + TextureHandle texture0 = m_RenderGraph.CreateTexture(new TextureDesc(kWidth, kHeight) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); TextureHandle depthTexture = m_RenderGraph.CreateTexture(new TextureDesc(kWidth, kHeight) { colorFormat = GraphicsFormat.D16_UNorm }); // no depth with Tile Properties @@ -987,7 +988,7 @@ public void RenderGraphTilePropertiesWorksWhenItsLast() TextureHandle texture0 = m_RenderGraph.CreateTexture(new TextureDesc(kWidth, kHeight) { colorFormat = GraphicsFormat.R8G8B8A8_UNorm }); TextureHandle depthTexture = m_RenderGraph.CreateTexture(new TextureDesc(kWidth, kHeight) { colorFormat = GraphicsFormat.D16_UNorm }); - + // no Tile Properties using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass0", out var passData)) { @@ -1006,7 +1007,7 @@ public void RenderGraphTilePropertiesWorksWhenItsLast() } var result = m_RenderGraph.CompileNativeRenderGraph(m_RenderGraph.ComputeGraphHash()); - var passes = result.contextData.GetNativePasses(); + var passes = result.contextData.GetNativePasses(); // TilePrperties flag should be added to subpass 0 and not interfere with merging. Assert.AreEqual(1, passes.Count); // 1 native Pass Assert.True(result.contextData.nativeSubPassData[0].flags.HasFlag(SubPassFlags.TileProperties)); @@ -1079,6 +1080,11 @@ public void RenderGraphMultisampledShaderResolvePassWorks() { m_RenderGraphTestPipeline.recordRenderGraphBody = (context, camera, cmd) => { + if (!SystemInfo.supportsMultisampledShaderResolve) + { + return; // Skip the test if the platform does not support multisampled shader resolve + } + var colorTexDesc = new TextureDesc(Vector2.one, false, false) { width = 4, @@ -2023,8 +2029,14 @@ public void CastToRTHandle_WithMemorylessResource() transientColorRTHandle = (RTHandle)data.transientColorOutputHandle; }); + if (!SystemInfo.supportsMemorylessTextures) + { + Assert.IsTrue(createdDepthRTHandle.rt.memorylessMode == RenderTextureMemoryless.None); + Assert.IsTrue(createdColorRTHandle.rt.memorylessMode == RenderTextureMemoryless.None); + Assert.IsTrue(transientColorRTHandle.rt.memorylessMode == RenderTextureMemoryless.None); + } // And let's make sure that the RTHandles are memoryless, i.e. no memory is allocated in system memory - if (msaaSamples != MSAASamples.None) + else if (msaaSamples != MSAASamples.None) { Assert.IsTrue(createdDepthRTHandle.rt.memorylessMode == (RenderTextureMemoryless.Depth | RenderTextureMemoryless.MSAA)); Assert.IsTrue(createdColorRTHandle.rt.memorylessMode == (RenderTextureMemoryless.Color | RenderTextureMemoryless.MSAA)); @@ -2099,5 +2111,225 @@ public void ResourcePool_TryGet() texturePool.Cleanup(); } + + TextureDesc SimpleTextureDesc(string name, int w, int h, int samples) + { + TextureDesc result = new TextureDesc(w, h); + result.msaaSamples = (MSAASamples)samples; + result.format = GraphicsFormat.R8G8B8A8_UNorm; + result.name = name; + return result; + } + + class TestRenderTargets + { + public TextureHandle backBuffer; + public TextureHandle depthBuffer; + public TextureHandle[] extraTextures = new TextureHandle[10]; + public TextureHandle extraDepthBuffer; + public TextureHandle extraDepthBufferBottomLeft; + }; + + TestRenderTargets ImportAndCreateRenderTargets(RenderGraph g, TextureUVOrigin backBufferUVOrigin) + { + TestRenderTargets result = new TestRenderTargets(); + var backBuffer = BuiltinRenderTextureType.CameraTarget; + var backBufferHandle = RTHandles.Alloc(backBuffer, "Backbuffer Color"); + var depthBuffer = BuiltinRenderTextureType.Depth; + var depthBufferHandle = RTHandles.Alloc(depthBuffer, "Backbuffer Depth"); + var extraDepthBufferHandle = RTHandles.Alloc(depthBuffer, "Extra Depth Buffer"); + var extraDepthBufferBottomLeftHandle = RTHandles.Alloc(depthBuffer, "Extra Depth Buffer Bottom Left"); + + ImportResourceParams importParams = new ImportResourceParams(); + importParams.textureUVOrigin = backBufferUVOrigin; + + RenderTargetInfo importInfo = new RenderTargetInfo(); + RenderTargetInfo importInfoDepth = new RenderTargetInfo(); + importInfo.width = 1024; + importInfo.height = 768; + importInfo.volumeDepth = 1; + importInfo.msaaSamples = 1; + importInfo.format = GraphicsFormat.R16G16B16A16_SFloat; + result.backBuffer = g.ImportTexture(backBufferHandle, importInfo, importParams); + + importInfoDepth = importInfo; + importInfoDepth.format = GraphicsFormat.D32_SFloat_S8_UInt; + result.depthBuffer = g.ImportTexture(depthBufferHandle, importInfoDepth, importParams); + + importInfo.format = GraphicsFormat.D24_UNorm; + result.extraDepthBuffer = g.ImportTexture(extraDepthBufferHandle, importInfoDepth, importParams); + + importParams.textureUVOrigin = TextureUVOrigin.BottomLeft; + result.extraDepthBufferBottomLeft = g.ImportTexture(extraDepthBufferBottomLeftHandle, importInfoDepth, importParams); + + for (int i = 0; i < result.extraTextures.Length; i++) + { + result.extraTextures[i] = g.CreateTexture(SimpleTextureDesc("ExtraTexture" + i, 1024, 768, 1)); + } + + return result; + } + + class UVOriginPassData + { + public TextureUVOrigin backBufferUVOrigin; + public TextureHandle renderAttachment; + public TextureHandle inputAttachment; + } + + // Test the case we want to work, attachment reads connected to the backbuffer inherit the backbuffer uv origin. + [Test] + public void TextureUVOrigin_CheckBackbufferUVOriginInherited() + { + // We don't send the list of graphics commands to execute to avoid mistmatch attachment size errors in the native render pass layer due to the backbuffer usage + m_RenderGraphTestPipeline.invalidContextForTesting = true; + m_RenderGraphTestPipeline.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + m_RenderGraphTestPipeline.recordRenderGraphBody = (context, camera, cmd) => + { + // On !SystemInfo.graphicsUVStartsAtTop APIs everything simplifies as that matches Unity's texture reads so we can't test a TopLeft system there. + var backBufferUVOrigin = SystemInfo.graphicsUVStartsAtTop ? TextureUVOrigin.TopLeft : TextureUVOrigin.BottomLeft; + var renderTargets = ImportAndCreateRenderTargets(m_RenderGraph, backBufferUVOrigin); + + // Render something to 0 + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass0", out var passData)) + { + passData.backBufferUVOrigin = backBufferUVOrigin; + passData.renderAttachment = renderTargets.extraTextures[0]; + + builder.SetRenderAttachmentDepth(renderTargets.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // Check that the backbuffer UV origin is inherited by attachments. + Assert.AreEqual(context.GetTextureUVOrigin(data.renderAttachment), data.backBufferUVOrigin); + }); + } + + // Render to 1 reading from 0 as an attachment + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass1", out var passData)) + { + passData.backBufferUVOrigin = backBufferUVOrigin; + passData.inputAttachment = renderTargets.extraTextures[0]; + passData.renderAttachment = renderTargets.extraTextures[1]; + + //builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Read); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // Check that the backbuffer UV origin is inherited by attachments. + Assert.AreEqual(context.GetTextureUVOrigin(data.renderAttachment), data.backBufferUVOrigin); + Assert.AreEqual(context.GetTextureUVOrigin(data.inputAttachment), data.backBufferUVOrigin); + }); + } + + // Render to final buffer reading from 1 as an attachment + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass2", out var passData)) + { + passData.backBufferUVOrigin = backBufferUVOrigin; + passData.inputAttachment = renderTargets.extraTextures[1]; + passData.renderAttachment = renderTargets.backBuffer; + + //builder.SetRenderAttachmentDepth(buffers.depthBuffer, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Read); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // Check that the backbuffer UV origin is inherited by attachments. + Assert.AreEqual(context.GetTextureUVOrigin(data.renderAttachment), data.backBufferUVOrigin); + Assert.AreEqual(context.GetTextureUVOrigin(data.inputAttachment), data.backBufferUVOrigin); + }); + } + + var result = m_RenderGraph.CompileNativeRenderGraph(m_RenderGraph.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + Assert.AreEqual(1, passes.Count); + Assert.AreEqual(4, passes[0].attachments.size); + Assert.AreEqual(3, passes[0].numGraphPasses); + Assert.AreEqual(3, passes[0].numNativeSubPasses); + }; + + m_Camera.Render(); + + m_RenderGraph.Cleanup(); + } + + // Test that texture reads break the inherited UV origin from the backbuffer. + [Test] + public void TextureUVOrigin_CheckTextureReadBreaksBackbufferUVOriginInherited() + { + // On OpenGL based APIs (!SystemInfo.graphicsUVStartsAtTop) we can't perform this test as we always assume the origin is BottomLeft which is compatible with texture reads. + if (!SystemInfo.graphicsUVStartsAtTop) return; + + // We don't send the list of graphics commands to execute to avoid mistmatch attachment size errors in the native render pass layer due to the backbuffer usage + m_RenderGraphTestPipeline.invalidContextForTesting = true; + m_RenderGraphTestPipeline.renderTextureUVOriginStrategy = RenderTextureUVOriginStrategy.PropagateAttachmentOrientation; // Switch to the mode we want to test. + m_RenderGraphTestPipeline.recordRenderGraphBody = (context, camera, cmd) => + { + var backBufferUVOrigin = TextureUVOrigin.TopLeft; + var renderTargets = ImportAndCreateRenderTargets(m_RenderGraph, backBufferUVOrigin); + + // Render something to 0 + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass0", out var passData)) + { + passData.renderAttachment = renderTargets.extraTextures[0]; + + builder.SetRenderAttachmentDepth(renderTargets.extraDepthBufferBottomLeft, AccessFlags.Write); + builder.SetRenderAttachment(renderTargets.extraTextures[0], 0, AccessFlags.Write); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // 0 is read in the next pass as a unity texture so needs to be bottom left. + Assert.AreEqual(TextureUVOrigin.BottomLeft, context.GetTextureUVOrigin(data.renderAttachment)); + }); + } + + // Render to 1 reading from 0 as a texture + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass1", out var passData)) + { + passData.backBufferUVOrigin = backBufferUVOrigin; + passData.renderAttachment = renderTargets.extraTextures[1]; + + builder.SetRenderAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Write); + builder.UseTexture(renderTargets.extraTextures[0], AccessFlags.Read); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // Check that the backbuffer UV origin is inherited by attachment 1. + // 1 is read via attachments so could inherit the backbuffer attachment but will probably generate an exception for mixed UV origin, but only on platforms (not in the editor) where we are top left origin. + Assert.AreEqual(data.backBufferUVOrigin, context.GetTextureUVOrigin(data.renderAttachment)); + }); + } + + // Render to final buffer reading from 1 as an attachment + using (var builder = m_RenderGraph.AddRasterRenderPass("TestPass2", out var passData)) + { + passData.backBufferUVOrigin = backBufferUVOrigin; + passData.renderAttachment = renderTargets.backBuffer; + + builder.SetRenderAttachment(renderTargets.backBuffer, 0, AccessFlags.Write); + builder.SetInputAttachment(renderTargets.extraTextures[1], 0, AccessFlags.Read); + builder.SetRenderFunc(static (UVOriginPassData data, RasterGraphContext context) => + { + // Check the backbuffer is using the UV origin we expect + Assert.AreEqual(data.backBufferUVOrigin, context.GetTextureUVOrigin(data.renderAttachment)); + }); + } + + var result = m_RenderGraph.CompileNativeRenderGraph(m_RenderGraph.ComputeGraphHash()); + var passes = result.contextData.GetNativePasses(); + + Assert.AreEqual(2, passes.Count); + Assert.AreEqual(2, passes[0].attachments.size); + Assert.AreEqual(1, passes[0].numGraphPasses); + Assert.AreEqual(1, passes[0].numNativeSubPasses); + + Assert.AreEqual(2, passes[1].attachments.size); + Assert.AreEqual(2, passes[1].numGraphPasses); + Assert.AreEqual(2, passes[1].numNativeSubPasses); + }; + + m_Camera.Render(); + + m_RenderGraph.Cleanup(); + } } } diff --git a/Packages/com.unity.render-pipelines.core/Tests/Editor/Tools/MaterialUpgrader/MaterialUpgraderTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Editor/Tools/MaterialUpgrader/MaterialUpgraderTests.cs index edf579da95b..8ee07cf14a1 100644 --- a/Packages/com.unity.render-pipelines.core/Tests/Editor/Tools/MaterialUpgrader/MaterialUpgraderTests.cs +++ b/Packages/com.unity.render-pipelines.core/Tests/Editor/Tools/MaterialUpgrader/MaterialUpgraderTests.cs @@ -94,7 +94,7 @@ public static IEnumerable TestCases NotAvailableForUpgradeReason = MaterialUpgrader.GenerateReason(info4) } }, - ExpectedLog = $"Testing\r\nSkipping material: Standard Variant - {MaterialUpgrader.GenerateReason(info4)}\r\n" + ExpectedLog = "Testing" + Environment.NewLine + $"Skipping material: Standard Variant - {MaterialUpgrader.GenerateReason(info4)}" + Environment.NewLine }).SetName("Material is a variant and skipped"); } } diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing.meta new file mode 100644 index 00000000000..9088b0c03e9 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 810f21d4e5b50a9489b040b4763c01ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs new file mode 100644 index 00000000000..184493b9633 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs @@ -0,0 +1,112 @@ +#if UNITY_EDITOR + +using NUnit.Framework; +using System; +using Unity.Mathematics; +using UnityEngine.PathTracing.Lightmapping; + +namespace UnityEngine.PathTracing.Tests +{ + internal class ChartIdenticationTests + { + [Test] + public void TestUnionFind() + { + uint vertexCount = 8; + var vertexChartIds = new UInt32[vertexCount]; + var triangleIndices = new UInt32[] + { + 0, 1, 7, + 4, 5, 6, + 2, 6, 4, + 0, 3, 7 + }; + ChartIdentification.InitializeRepresentatives(vertexChartIds); + ChartIdentification.UnionTriangleEdges(triangleIndices, vertexChartIds); + ChartIdentification.FindRepresentatives(vertexChartIds); + + Assert.AreEqual(0, vertexChartIds[0]); + Assert.AreEqual(0, vertexChartIds[1]); + Assert.AreEqual(2, vertexChartIds[2]); + Assert.AreEqual(0, vertexChartIds[3]); + Assert.AreEqual(2, vertexChartIds[4]); + Assert.AreEqual(2, vertexChartIds[5]); + Assert.AreEqual(2, vertexChartIds[6]); + Assert.AreEqual(0, vertexChartIds[7]); + } + + [Test] + public void TestCompaction() + { + var vertexChartIds = new UInt32[] + { + 3, 3, 3, + 3, 2, 2, + 9, 9, 0, + 0, 3, 3 + }; + ChartIdentification.Compact(vertexChartIds, out uint chartCount); + + Assert.AreEqual(4, chartCount); + + Assert.AreEqual(0, vertexChartIds[0]); + Assert.AreEqual(0, vertexChartIds[1]); + Assert.AreEqual(0, vertexChartIds[2]); + Assert.AreEqual(0, vertexChartIds[3]); + Assert.AreEqual(1, vertexChartIds[4]); + Assert.AreEqual(1, vertexChartIds[5]); + Assert.AreEqual(2, vertexChartIds[6]); + Assert.AreEqual(2, vertexChartIds[7]); + Assert.AreEqual(3, vertexChartIds[8]); + Assert.AreEqual(3, vertexChartIds[9]); + Assert.AreEqual(0, vertexChartIds[10]); + Assert.AreEqual(0, vertexChartIds[11]); + } + + [Test] + public void TestDeduplication() + { + uint vertexCount = 6; + var vertexChartIds = new UInt32[vertexCount]; + var inputVertexUvs = new float2[] + { + new(-1.0f, 0.0f), + new(0.0f, 1.0f), + new(0.0f, -1.0f), + new(0.0f, 1.0f), + new(1.0f, 0.0f), + new(0.0f, -1.0f) + }; + var inputVertexNormals = new float3[] + { + new(-1.0f, 0.0f, 0.0f), + new(0.0f, 1.0f, 0.0f), + new(0.0f, -1.0f, 0.0f), + new(0.0f, 1.0f, 0.0f), + new(1.0f, 0.0f, 0.0f), + new(0.0f, -1.0f, 0.0f) + }; + var inputVertexPositions = new float3[] + { + new(-1.0f, 0.0f, 0.0f), + new(0.0f, 1.0f, 0.0f), + new(0.0f, -1.0f, 0.0f), + new(0.0f, 1.0f, 0.0f), + new(1.0f, 0.0f, 0.0f), + new(0.0f, -2.0f, 0.0f) + }; + + ChartIdentification.InitializeRepresentatives(vertexChartIds); + ChartIdentification.UnionDuplicateVertices(inputVertexUvs, inputVertexPositions, inputVertexNormals, vertexChartIds, true); + ChartIdentification.FindRepresentatives(vertexChartIds); + + Assert.AreEqual(0, vertexChartIds[0]); + Assert.AreEqual(3, vertexChartIds[1]); + Assert.AreEqual(2, vertexChartIds[2]); + Assert.AreEqual(3, vertexChartIds[3]); + Assert.AreEqual(4, vertexChartIds[4]); + Assert.AreEqual(5, vertexChartIds[5]); + } + } +} +#endif diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs.meta new file mode 100644 index 00000000000..87afc21bdd6 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/ChartIdentificationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a6482f5100464a5abfcfce1521a3baf0 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader new file mode 100644 index 00000000000..640beaec706 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader @@ -0,0 +1,68 @@ +#define UNIFIED_RT_GROUP_SIZE_X 16 +#define UNIFIED_RT_GROUP_SIZE_Y 8 +#define UNIFIED_RT_RAYGEN_FUNC TestRadianceEstimation + +#include "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl" + +uint g_SampleCount; + +struct TestRay +{ + float3 origin; + float3 direction; +}; + +StructuredBuffer _InputRay; +RWStructuredBuffer _Output; + +float3 EstimateIncomingRadiance(UnifiedRT::DispatchInfo dispatchInfo, float3 rayOrigin, float3 rayDirection, uint sampleCount, uint bounceCount, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer instanceList) +{ + PathTracingSampler rngState; + rngState.Init(uint2(0, 0), 0); + + PathIterator pathIter; + float3 sampleSum = 0; + for (uint sampleIdx = 0; sampleIdx < sampleCount; ++sampleIdx) + { + UnifiedRT::Ray ray; + ray.origin = rayOrigin; + ray.direction = rayDirection; + ray.tMin = 0; + ray.tMax = FLT_INF; + InitPathIterator(pathIter, ray); + + int transparencyBounce = 0; + const int maxTransparencyBounces = 6; + for (uint bounceIndex = 0; bounceIndex <= bounceCount && transparencyBounce < maxTransparencyBounces; bounceIndex++) + { + uint traceResult = TraceBounceRayAndAddRadiance(pathIter, bounceIndex, RayMask(bounceIndex == 0), ShadowRayMask(), dispatchInfo, accelStruct, instanceList, rngState); + + if (traceResult == TRACE_MISS) + { + break; + } + if (traceResult == TRACE_TRANSMISSION) + { + bounceIndex--; + transparencyBounce++; + continue; + } + + if (!Scatter(pathIter, rngState)) + break; + + rngState.NextBounce(); + } + + sampleSum += pathIter.radianceSample; + } + + return sampleSum / float(sampleCount); +} + +void TestRadianceEstimation(UnifiedRT::DispatchInfo dispatchInfo) +{ + UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct); + TestRay ray = _InputRay[0]; + _Output[0] = EstimateIncomingRadiance(dispatchInfo, ray.origin, ray.direction, g_SampleCount, g_BounceCount, accelStruct, g_AccelStructInstanceList); +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader.meta new file mode 100644 index 00000000000..6e39c0c1d31 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 6e0b547bab30a804c8123974120d583f +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs new file mode 100644 index 00000000000..a6ff3bcabda --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs @@ -0,0 +1,242 @@ +// These tests relies on RayTracingResources.Load() so cannot run in a player. +#if UNITY_EDITOR + +using System.Runtime.InteropServices; +using NUnit.Framework; +using Unity.Mathematics; +using UnityEditor; +using UnityEngine.PathTracing.Core; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Sampling; +using UnityEngine.Rendering.UnifiedRayTracing; + +namespace UnityEngine.PathTracing.Tests +{ + internal class PathIteratorTests + { + private static class MeshUtil + { + internal static Mesh CreateQuadMesh() + { + Mesh mesh = new Mesh(); + + Vector3[] vertices = + { + new(-0.5f, -0.5f, 0.0f), + new(0.5f, -0.5f, 0.0f), + new(-0.5f, 0.5f, 0.0f), + new(0.5f, 0.5f, 0.0f) + }; + mesh.vertices = vertices; + + Vector3[] normals = + { + Vector3.forward, + Vector3.forward, + Vector3.forward, + Vector3.forward + }; + mesh.normals = normals; + + Vector2[] uv = + { + new(0, 0), + new(1, 0), + new(0, 1), + new(1, 1) + }; + mesh.uv = uv; + + int[] tris = + { + 0, 2, 1, + 2, 3, 1 + }; + mesh.triangles = tris; + + return mesh; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct TestRay + { + public float3 Origin; + public float3 Direction; + } + + private World _world; + private RayTracingContext _rayTracingContext; + private CommandBuffer _cmd; + private SamplingResources _samplingResources; + private RTHandle _emptyExposureTexture; + + private const RayTracingBackend _backend = RayTracingBackend.Compute; + + [SetUp] + public void Setup() + { + if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore || + SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3) + { + Assert.Ignore("PathTracingCore is incompatible with OpenGL as it places a harsh limitation of buffer count."); + return; + } + + var resources = new RayTracingResources(); + resources.Load(); + _rayTracingContext = new RayTracingContext(_backend, resources); + + _cmd = new CommandBuffer(); + _cmd.name = "PathTracingCore/PathIteratorTests"; + + var worldResources = new WorldResourceSet(); + worldResources.LoadFromAssetDatabase(); + _world = new World(); + _world.Init(_rayTracingContext, worldResources); + + _samplingResources = new SamplingResources(); + _samplingResources.Load((uint)SamplingResources.ResourceType.All); + + _emptyExposureTexture = RTHandles.Alloc(1, 1, enableRandomWrite: true, name: "Empty EV100 Exposure"); + } + + [TearDown] + public void TearDown() + { + _world?.Dispose(); + _rayTracingContext?.Dispose(); + _cmd?.Dispose(); + _emptyExposureTexture?.Release(); + + _samplingResources?.Dispose(); + } + + [Test] + [TestCase(1, 1, 1, 1u)] + [TestCase(10, 20, 30, 1u)] + [TestCase(10, 20, 30, 8u)] + [TestCase(0, 42, 0, 1u)] + [TestCase(0, 42, 0, 8u)] + public void EmptyWorldWithEnvironmentLight_ShouldOutputEnvironmentLight(float envRed, float envGreen, float envBlue, uint sampleCount) + { + using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf()); + { + var inputHost = new Ray[1]; + inputHost[0].origin = float3.zero; + inputHost[0].direction = new float3(0, 1, 0); + deviceInputBuffer.SetData(inputHost); + } + using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3); + + var cubemapMat = new Material(Shader.Find("PathIteratorTesting/UniformCubemap")); + cubemapMat.SetVector("_Radiance", new Vector4(envRed, envGreen, envBlue, 1.0f)); + _world.SetEnvironmentMaterial(cubemapMat); + GraphicsBuffer buildScratchBuffer = null; + _world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true); + var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader"); + + Util.BindWorld(_cmd, shader, _world, 8); + Util.BindPathTracingInputs(_cmd, shader, false, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture); + + shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount); + shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer); + shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer); + + GraphicsBuffer traceScratchBuffer = null; + RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer); + shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1); + + Graphics.ExecuteCommandBuffer(_cmd); + + var hostOutputBuffer = new float3[1]; + deviceOutputBuffer.GetData(hostOutputBuffer); + + const float tolerance = 0.001f; + Assert.AreEqual(envRed, hostOutputBuffer[0].x, tolerance); + Assert.AreEqual(envGreen, hostOutputBuffer[0].y, tolerance); + Assert.AreEqual(envBlue, hostOutputBuffer[0].z, tolerance); + + CoreUtils.Destroy(cubemapMat); + buildScratchBuffer.Dispose(); + traceScratchBuffer.Dispose(); + } + + [Test] + [TestCase(1, 0, 0, 1u)] + [TestCase(0, 1, 0, 1u)] + [TestCase(0, 0, 1, 1u)] + [TestCase(0, 0, 1, 8u)] + public void RayHittingPlaneLitByWhiteEnvironmentLight_ShouldMatchAnalyticDerivation(float albedoRed, float albedoGreen, float albedoBlue, uint sampleCount) + { + Assert.Ignore("Fails on MacEditor ARM64, Windows x64 and other platforms, probably due to it looking for output in the wrong place. See https://jira.unity3d.com/browse/GFXLIGHT-1796"); + using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf()); + { + var hostInputBuffer = new Ray[1]; + hostInputBuffer[0].origin = new float3(0.1f, 0.1f, -1); // We avoid numerical precision issues at the diagonal. + hostInputBuffer[0].direction = new float3(0, 0, 1); + deviceInputBuffer.SetData(hostInputBuffer); + } + using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3); + + var cubemapMaterial = new Material(Shader.Find("PathIteratorTesting/UniformCubemap")); + cubemapMaterial.SetVector("_Radiance", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + GraphicsBuffer buildScratchBuffer = null; + + var mesh = MeshUtil.CreateQuadMesh(); + var material = new Material(Shader.Find("PathIteratorTesting/UniformAlbedoMetaPass")); + material.SetColor("_Albedo", new Color(albedoRed, albedoGreen, albedoBlue, 1)); + + var matDesc = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material); + + _world.SetEnvironmentMaterial(cubemapMaterial); + var matHandle = _world.AddMaterial(matDesc, UVChannel.UV0); + + var mask = World.GetInstanceMask(ShadowCastingMode.On, true, RenderedGameObjectsFilter.OnlyStatic); + + _world.AddInstance( + mesh, + new [] { matHandle }, + new[] { mask }, + 1, + Matrix4x4.identity, + new Bounds(Vector3.zero, Vector3.one * 20.0f), + true, + RenderedGameObjectsFilter.OnlyStatic, + true); + _world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true); + + var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader"); + + Util.BindWorld(_cmd, shader, _world, 8); + Util.BindPathTracingInputs(_cmd, shader, false, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture); + + shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount); + shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer); + shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer); + + GraphicsBuffer traceScratchBuffer = null; + RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer); + shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1); + + Graphics.ExecuteCommandBuffer(_cmd); + + var outputHost = new float3[1]; + deviceOutputBuffer.GetData(outputHost); + + // We have a non-emissive lambertian plane with albedo c which receives light from a uniform environment light with radiance 1. How much light is reflected in the direction of the normal of the plane? + // The incoming light L_i is 1 and the BRDF is c/π. + // L_o = ∫ c/π L_i(ω) cos(θ) dω = c/π ∫ cos(θ) dω = c. + // The last equality holds because the hemispherical integral of cos(θ) is π. + Assert.AreEqual(albedoRed, outputHost[0].x); + Assert.AreEqual(albedoGreen, outputHost[0].y); + Assert.AreEqual(albedoBlue, outputHost[0].z); + + CoreUtils.Destroy(cubemapMaterial); + CoreUtils.Destroy(material); + buildScratchBuffer.Dispose(); + traceScratchBuffer.Dispose(); + } + } +} +#endif diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs.meta new file mode 100644 index 00000000000..ae2e48f925d --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3745144d19d6f469e96d89ae8647a702 \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader new file mode 100644 index 00000000000..6bdd17792ea --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader @@ -0,0 +1,68 @@ +Shader "PathIteratorTesting/UniformAlbedoMetaPass" +{ + Properties { + _Albedo ("Albedo", Color) = (.5, .5, .5, 1.0) + } + SubShader + { + Pass + { + Name "META" + Tags { "LightMode" = "Meta" } + Cull Off + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma shader_feature _EMISSION + + #include "UnityCG.cginc" + #include "UnityMetaPass.cginc" + + float4 _Albedo; + + struct appdata + { + float4 vertex : POSITION; + float2 uv1 : TEXCOORD1; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float4 pos : SV_POSITION; + float2 uv1 : TEXCOORD1; + }; + + v2f vert(appdata v) + { + v2f o; + if (unity_MetaVertexControl.x) + { + o.pos.xy = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw; + o.pos.zw = 0; + } + else + { + o.pos = mul(UNITY_MATRIX_VP, float4(v.vertex.xyz, 1.0)); + } + o.uv1 = v.uv1; + return o; + } + + float4 frag(v2f i) : SV_Target + { + if (unity_MetaFragmentControl.x) + { + return _Albedo; + } + if (unity_MetaFragmentControl.y) + { + return 0; + } + return 0; + } + ENDCG + } + } +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader.meta new file mode 100644 index 00000000000..874e72aa517 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformAlbedoMetaPass.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3990aa41cea4e4fa79ec155970b62e32 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader new file mode 100644 index 00000000000..e1922772a65 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader @@ -0,0 +1,43 @@ +Shader "PathIteratorTesting/UniformCubemap" { + Properties { + _Radiance ("Radiance", Vector) = (.5, .5, .5, 1.0) + } + + SubShader { + Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" } + Cull Off ZWrite Off + + Pass { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + half4 _Radiance; + + struct appdata_t { + float4 vertex : POSITION; + }; + + struct v2f { + float4 vertex : SV_POSITION; + }; + + v2f vert (appdata_t v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex.xyz); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + return fixed4(_Radiance.xyz, 1.0); + } + ENDCG + } + } + + Fallback Off +} diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader.meta new file mode 100644 index 00000000000..cf0d87b1308 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/UniformCubemap.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 397e4e1ab7fcb41b9842c2e464a0d58a +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef new file mode 100644 index 00000000000..51201104881 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef @@ -0,0 +1,23 @@ +{ + "name": "Unity.PathTracing.Runtime.Tests", + "rootNamespace": "", + "references": [ + "GUID:214c0945bb158c940aada223f3223ee8", + "GUID:c49c619b6af2be941af9bcbca2641964", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:df380645f10b7bc4b97d4f5eb6303d95" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef.meta new file mode 100644 index 00000000000..c1ace54d1d1 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7c4d0a73126b14e2693931e0c68b75e7 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj new file mode 100644 index 00000000000..8d9d2490408 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj @@ -0,0 +1,20 @@ + + + + false + true + Unity.PathTracing.Runtime.Tests + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj.meta b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj.meta new file mode 100644 index 00000000000..d9d5f420eb6 --- /dev/null +++ b/Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/Unity.PathTracing.Runtime.Tests.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c3616d38e7c2446fad048d0f1b116a51 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.render-pipelines.core/package.json b/Packages/com.unity.render-pipelines.core/package.json index de0c130ef0e..05096d13448 100644 --- a/Packages/com.unity.render-pipelines.core/package.json +++ b/Packages/com.unity.render-pipelines.core/package.json @@ -1,16 +1,15 @@ { "name": "com.unity.render-pipelines.core", "description": "SRP Core makes it easier to create or customize a Scriptable Render Pipeline (SRP). SRP Core contains reusable code, including boilerplate code for working with platform-specific graphics APIs, utility functions for common rendering operations, and shader libraries. The code in SRP Core is use by the High Definition Render Pipeline (HDRP) and Universal Render Pipeline (URP). If you are creating a custom SRP from scratch or customizing a prebuilt SRP, using SRP Core will save you time.", - "version": "17.3.0", - "unity": "6000.3", + "version": "17.4.0", + "unity": "6000.4", "displayName": "Scriptable Render Pipeline Core", "dependencies": { "com.unity.burst": "1.8.14", "com.unity.mathematics": "1.3.2", "com.unity.ugui": "2.0.0", "com.unity.collections": "2.4.3", - "com.unity.modules.physics": "1.0.0", "com.unity.modules.terrain": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" } -} \ No newline at end of file +} diff --git a/Packages/com.unity.render-pipelines.high-definition-config/CHANGELOG.md b/Packages/com.unity.render-pipelines.high-definition-config/CHANGELOG.md index 4d1d9dd9d30..3949e5c7e6f 100644 --- a/Packages/com.unity.render-pipelines.high-definition-config/CHANGELOG.md +++ b/Packages/com.unity.render-pipelines.high-definition-config/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Version Updated The version number for this package has increased due to a version update of a related graphics package. +## [17.3.0] - 2025-08-27 + +This version is compatible with Unity 6000.3.0b1. +For the release notes, refer to the [Unity download archive](https://unity.com/releases/editor/archive). + ## [17.2.0] - 2025-05-14 This version is compatible with Unity 6000.2.0b2. diff --git a/Packages/com.unity.render-pipelines.high-definition-config/package.json b/Packages/com.unity.render-pipelines.high-definition-config/package.json index 771d16ffcec..d386a14c318 100644 --- a/Packages/com.unity.render-pipelines.high-definition-config/package.json +++ b/Packages/com.unity.render-pipelines.high-definition-config/package.json @@ -1,10 +1,10 @@ { "name": "com.unity.render-pipelines.high-definition-config", "description": "Configuration files for the High Definition Render Pipeline.", - "version": "17.3.0", - "unity": "6000.3", + "version": "17.4.0", + "unity": "6000.4", "displayName": "High Definition Render Pipeline Config", "dependencies": { - "com.unity.render-pipelines.core": "17.3.0" + "com.unity.render-pipelines.core": "17.4.0" } } \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.high-definition/CHANGELOG.md b/Packages/com.unity.render-pipelines.high-definition/CHANGELOG.md index c9fda347f42..823d600e9ca 100644 --- a/Packages/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/Packages/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Version Updated The version number for this package has increased due to a version update of a related graphics package. +## [17.3.0] - 2025-08-27 + +This version is compatible with Unity 6000.3.0b1. +For the release notes, refer to the [Unity download archive](https://unity.com/releases/editor/archive). + ## [17.2.0] - 2025-05-14 This version is compatible with Unity 6000.2.0b2. diff --git a/Packages/com.unity.render-pipelines.high-definition/Documentation~/Rendering-Layers.md b/Packages/com.unity.render-pipelines.high-definition/Documentation~/Rendering-Layers.md index 22edb89fffd..0987714724c 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Documentation~/Rendering-Layers.md +++ b/Packages/com.unity.render-pipelines.high-definition/Documentation~/Rendering-Layers.md @@ -72,6 +72,7 @@ Assigning the bulb’s Mesh Renderer to a specific Rendering Layer means that th ![To restore the transmission effect, create a Point Light and assign it to the same Rendering Layer as the bulb’s Mesh Renderer. Now this Point Light only affects the bulb’s Mesh Renderer and does not contribute to the rest of the Scene Lighting.](Images/LightLayers3.png)
To restore the transmission effect, create a Point Light and assign it to the same Rendering Layer as the bulb’s Mesh Renderer. Now this Point Light only affects the bulb’s Mesh Renderer and does not contribute to the rest of the Scene Lighting. +## Additional resources -For more information on this process, see Pierre Donzallaz’s [expert guide](https://pydonzallaz.files.wordpress.com/2019/02/create-high-quality-light-fixtures-in-unity.pdf) on creating high quality light fixtures in Unity. +- [Pierre Donzallaz - Expert guide: Create High-Quality  Light Fixtures in Unity](https://pydonzallaz.files.wordpress.com/2019/02/create-high-quality-light-fixtures-in-unity.pdf) diff --git a/Packages/com.unity.render-pipelines.high-definition/Documentation~/Understand-Fog.md b/Packages/com.unity.render-pipelines.high-definition/Documentation~/Understand-Fog.md index a1471f2239a..8c20b22cd88 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Documentation~/Understand-Fog.md +++ b/Packages/com.unity.render-pipelines.high-definition/Documentation~/Understand-Fog.md @@ -12,3 +12,4 @@ Instead of using a constant color, fog can use the background sky as a source fo Optionally, you can enable volumetric fog for GameObjects close to the camera. It realistically simulates the interaction of lights with fog, which allows for physically plausible rendering of glow and crepuscular rays, which are beams of light that stream through gaps in objects like clouds and trees from a central point. +**Note:** Volumetric fog doesn't support [light rendering layers](Rendering-Layers). diff --git a/Packages/com.unity.render-pipelines.high-definition/Documentation~/rendering-debugger-window-reference.md b/Packages/com.unity.render-pipelines.high-definition/Documentation~/rendering-debugger-window-reference.md index 6c90650cf44..e91cb73e060 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Documentation~/rendering-debugger-window-reference.md +++ b/Packages/com.unity.render-pipelines.high-definition/Documentation~/rendering-debugger-window-reference.md @@ -116,7 +116,7 @@ The **Material** panel has tools that you can use to visualize different Materia | **Attributes** | N/A | Use the drop-down to select a 3D GameObject attribute, like Texture Coordinates or Vertex Color, to visualize on screen. | | **Properties** | N/A | Use the drop-down to select a property that the debugger uses to highlight GameObjects on screen. The debugger highlights GameObjects that use a Material with the property that you select. | | **GBuffer** | N/A | Use the drop-down to select a property to visualize from the GBuffer for deferred Materials. | -| **Material Validator** | N/A | Use the drop-down to select properties to display validation colors for:
  • Diffuse Color: Select this option to check if the diffuse colors in your Scene adhere to an acceptable PBR range. If the Material color is out of this range, the debugger displays it in the Too High Color color if it's above the range, or in the Too Low Color if it's below the range.
  • Metal or SpecularColor: Select this option to check if a pixel contains a metallic or specular color that adheres to an acceptable PBR range. If it doesn't, the debugger highlights it in the Not A Pure Metal Color. For information about the acceptable PBR ranges in Unity, see the Material Charts documentation.
| +| **Material Validator** | N/A | Display validation colors for a selected material property:
  • Diffuse Color: Select this option to check if the diffuse colors in your scene adhere to an acceptable PBR range. If the material color is out of this range, the debugger displays it in the **Too High Color** color if it's above the range, or in the **Too Low Color** if it's below the range.
  • Metal or SpecularColor: Select this option to check if a pixel contains a metallic or specular color that adheres to an acceptable PBR range. If it doesn't, the debugger highlights it in the **Not A Pure Metal Color**. For information about the acceptable PBR ranges in Unity, refer to the [Material Charts](https://docs.unity3d.com/Manual/StandardShaderMaterialCharts.html) documentation.
| | **Material Validator** | **Too High Color** | Use the color picker to select the color that the debugger displays when a Material's diffuse color is above the acceptable PBR range. | | **Material Validator** | **Too Low Color** | Use the color picker to select the color that the debugger displays when a Material's diffuse color is below the acceptable PBR range.
  • This property only appears when you select Diffuse Color or Metal or SpecularColor from the Material Validator drop-down.
| | **Material Validator** | **Not A Pure Metal Color** | Use the color picker to select the color that the debugger displays if a pixel defined as metallic has a non-zero albedo value. The debugger only highlights these pixels if you enable the True Metals checkbox.
  • This property only appears when you select Diffuse Color or Metal or SpecularColor from the Material Validator drop-down.
| @@ -134,7 +134,7 @@ The **Lighting** panel has tools that you can use to visualize various component | **Shadow Debug Option** | **Sub-option** | **Description** | |------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Debug Mode** | N/A | Use the drop-down to select which shadow debug information to overlay on the screen:
  • None: Select this mode to remove the shadow debug information from the screen.
  • VisualizePunctualLightAtlas: Select this mode to overlay the shadow atlas for Punctual Lights in your Scene.
  • VisualizeDirectionalLightAtlas: Select this mode to overlay the shadow atlas for Directional Lights in your Scene.
  • VisualizeAreaLightAtlas: Select this mode to overlay the shadow atlas for Area Lights in your Scene.
  • VisualizeShadowMap: Select this mode to overlay a single shadow map for a Light in your Scene.
  • SingleShadow: Select this mode to replace the Scene's lighting with a single Light. To select which Light to isolate, see Use Selection or Shadow Map Index.
| +| **Debug Mode** | N/A | Select the shadow debug information to display on the screen:
  • None: Select this mode to remove the shadow debug information from the screen.
  • VisualizePunctualLightAtlas: Select this mode to overlay the shadow atlas for Punctual Lights in your scene.
  • VisualizeDirectionalLightAtlas: Select this mode to overlay the shadow atlas for Directional Lights in your scene.
  • VisualizeAreaLightAtlas: Select this mode to overlay the shadow atlas for Area Lights in your scene.
  • VisualizeShadowMap: Select this mode to overlay a single shadow map for a light in your scene.
  • SingleShadow: Select this mode to replace the scene's lighting with a single light. To select which light to isolate, refer to Use Selection or Shadow Map Index.
| | **Debug Mode** | **Use Selection** | Enable the checkbox to display the shadow map for the Light you select in the Scene.
  • This property only appears when you select VisualizeShadowMap or SingleShadow from the Shadow Debug Mode drop-down.
| | **Debug Mode** | **Shadow Map Index** | Use the slider to select the index of the shadow map to view. To use this property correctly, you must have at least one Light in your Scene that uses shadow maps. | | **Global Scale Factor** | N/A | Use the slider to set the global scale that HDRP applies to the shadow rendering resolution. | @@ -146,7 +146,7 @@ The **Lighting** panel has tools that you can use to visualize various component | **Lighting Debug Option** | **Description** | |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Show Lights By Type** | Allows the user to enable or disable lights in the scene based on their type.
  • Directional Lights: Enable the checkbox to see Directional Lights in your Scene. Disable this checkbox to remove Directional Lights from your Scene's lighting.
  • Punctual Lights: Enable the checkbox to see Punctual Lights in your Scene. Disable this checkbox to remove Punctual Lights from your Scene's lighting.
  • Area Lights: Enable the checkbox to see Area Lights in your Scene. Disable this checkbox to remove Area Lights from your Scene's lighting.
  • Reflection Probes: Enable the checkbox to see Reflection Probes in your Scene. Disable this checkbox to remove Reflection Probes from your Scene's lighting.
| -| **Exposure** | Allows you to select an Exposure debug mode to use.
  • Debug Mode: Use the drop-down to select a debug mode. See Exposure documentation for more information.
  • Show Tonemap Curve: Enable the checkbox to overlay the tonemap curve to the histogram debug view. This property only appears when you select HistogramView from Debug Mode.
  • Center Around Exposure: Enable the checkbox to center the histogram around the current exposure value. This property only appears when you select HistogramView from Debug Mode.
  • Display RGB Histogram: Enable the checkbox to display the Final Image Histogram as an RGB histogram instead of just luminance. This property only appears when you select FinalImageHistogramView from Debug Mode.
  • Display Mask Only: Enable the checkbox to display only the metering mask in the picture-in-picture. When disabled, the mask displays after weighting the scene color instead. This property only appears when you select MeteringWeighted from Debug Mode.
  • Debug Exposure Compensation: Set an additional exposure compensation for debug purposes.
| +| **Exposure** | Select an Exposure debug mode to use.
  • Debug Mode: Select a debug mode from the available options. Refer to [Control exposure](Override-Exposure.md) for more information.
  • Show Tonemap Curve: Enable this to overlay the Tonemap Curve to the histogram debug view. This property only appears when you select HistogramView from Debug Mode.
  • Center Around Exposure: Enable this to center the histogram around the current exposure value. This property only appears when you select HistogramView from Debug Mode.
  • Display RGB Histogram: Enable this to display the Final Image Histogram as an RGB histogram instead of just luminance. This property only appears when you select FinalImageHistogramView from Debug Mode.
  • Display Mask Only: Enable this to display only the metering mask in the picture-in-picture. When disabled, the mask displays after weighting the scene color instead. This property only appears when you select MeteringWeighted from Debug Mode.
  • Debug Exposure Compensation: Set an additional exposure compensation for debug purposes.
| | **Debug Mode** | Use the drop-down to select a lighting mode to debug. For example, you can visualize diffuse lighting, specular lighting, direct diffuse lighting, direct specular lighting, indirect diffuse lighting, indirect specular lighting, emissive lighting and Directional Light shadow cascades. | | **Hierarchy Debug Mode** | Use the drop-down to select a light type to display the direct lighting for or a Reflection Probe type to display the indirect lighting for. | diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs index edbe2ae255a..fc7f12cbaa9 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs @@ -433,7 +433,7 @@ static void DrawShapeContent(SerializedHDLight serialized, Editor owner) // If light unit is currently displayed in lumen and 'reflector' is on, recalculate candela so lumen value remains constant bool isReflectorRelevant = serialized.settings.enableSpotReflector.boolValue && serialized.settings.lightUnit.GetEnumValue() == LightUnit.Lumen; - bool needsReflectedIntensityRecalc = false; + bool updateSpotAngles = false; int indent = EditorGUI.indentLevel; @@ -464,8 +464,7 @@ static void DrawShapeContent(SerializedHDLight serialized, Editor owner) min = EditorGUI.DelayedFloatField(minRect, min); if (EditorGUI.EndChangeCheck()) { - serialized.customSpotLightShadowCone.floatValue = Math.Min(serialized.customSpotLightShadowCone.floatValue, serialized.settings.spotAngle.floatValue); - min = Mathf.Clamp(min, HDAdditionalLightData.k_MinSpotAngle, max); + updateSpotAngles = true; serialized.settings.innerSpotAngle.floatValue = min; } @@ -473,29 +472,39 @@ static void DrawShapeContent(SerializedHDLight serialized, Editor owner) EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, HDAdditionalLightData.k_MinSpotAngle,HDAdditionalLightData.k_MaxSpotAngle ); if (EditorGUI.EndChangeCheck()) { - min = Mathf.Clamp(min, HDAdditionalLightData.k_MinSpotAngle, max); - serialized.settings.innerSpotAngle.floatValue = min; serialized.settings.spotAngle.floatValue = max; - serialized.settings.bakedShadowRadiusProp.floatValue = serialized.shapeRadius.floatValue; - needsReflectedIntensityRecalc = (max != oldSpotAngle); + serialized.settings.innerSpotAngle.floatValue = min; + updateSpotAngles = true; } EditorGUI.BeginChangeCheck(); EditorGUI.DelayedFloatField(maxRect, serialized.settings.spotAngle, GUIContent.none); if (EditorGUI.EndChangeCheck()) { - needsReflectedIntensityRecalc = true; + updateSpotAngles = true; } - if (isReflectorRelevant && needsReflectedIntensityRecalc) + if (updateSpotAngles) { - // If light unit is currently displayed in lumen and 'reflector' is on and the spot angle has changed, - // recalculate candela so lumen value remains constant - float oldSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(oldSpotAngle); - float oldLumen = LightUnitUtils.CandelaToLumen(serialized.settings.intensity.floatValue, oldSolidAngle); - float newSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(serialized.settings.spotAngle.floatValue); - float newCandela = LightUnitUtils.LumenToCandela(oldLumen, newSolidAngle); - serialized.settings.intensity.floatValue = newCandela; + // Clamp outer spot angle + serialized.settings.spotAngle.floatValue = Mathf.Max(HDAdditionalLightData.k_MinSpotAngle, serialized.settings.spotAngle.floatValue); + // Clamp inner spot angle + serialized.settings.innerSpotAngle.floatValue = Mathf.Clamp(serialized.settings.innerSpotAngle.floatValue, HDAdditionalLightData.k_MinSpotAngle, serialized.settings.spotAngle.floatValue); + // Update other dependent values + serialized.customSpotLightShadowCone.floatValue = Mathf.Min(serialized.customSpotLightShadowCone.floatValue, serialized.settings.spotAngle.floatValue); + serialized.settings.bakedShadowRadiusProp.floatValue = serialized.shapeRadius.floatValue; + + if (isReflectorRelevant) + { + // If light unit is currently displayed in lumen and 'reflector' is + // on and the spot angle has changed, recalculate candela so lumen + // value remains constant + float oldSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(oldSpotAngle); + float oldLumen = LightUnitUtils.CandelaToLumen(serialized.settings.intensity.floatValue, oldSolidAngle); + float newSolidAngle = LightUnitUtils.GetSolidAngleFromSpotLight(serialized.settings.spotAngle.floatValue); + float newCandela = LightUnitUtils.LumenToCandela(oldLumen, newSolidAngle); + serialized.settings.intensity.floatValue = newCandela; + } } EditorGUI.indentLevel = indent - 1; diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/PBRSky/ShaderGraph/PBRSkySubTarget.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/PBRSky/ShaderGraph/PBRSkySubTarget.cs index d5c7ed54756..e2fca31b489 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/PBRSky/ShaderGraph/PBRSkySubTarget.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/PBRSky/ShaderGraph/PBRSkySubTarget.cs @@ -311,7 +311,7 @@ static void CreatePBRSkyGraph() */ // Copy the default graph from the package - ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "PBR Sky Shader Graph.shadergraph", null, null); + ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "PBR Sky Shader Graph.shadergraph", ShaderGraphImporter.GetIcon(), null); } } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/AdvancedOptionsPropertyBlock.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/AdvancedOptionsPropertyBlock.cs index c0caf5763ab..e8de62f26d3 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/AdvancedOptionsPropertyBlock.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/AdvancedOptionsPropertyBlock.cs @@ -21,6 +21,10 @@ public enum Features { None = 0, SpecularOcclusion = 1 << 0, + OverrideBakedGI = 1 << 1, + LodCrossfade = 1 << 2, + PrecomputedVelocity = 1 << 3, + DebugSymbols = 1 << 4, StackLit = All ^ SpecularOcclusion, All = ~0 @@ -46,12 +50,15 @@ protected override void CreatePropertyGUI() { if ((enabledFeatures & Features.SpecularOcclusion) != 0) AddProperty(specularOcclusionModeText, () => lightingData.specularOcclusionMode, (newValue) => lightingData.specularOcclusionMode = newValue); - AddProperty(Styles.overrideBakedGI, () => lightingData.overrideBakedGI, (newValue) => lightingData.overrideBakedGI = newValue); + if ((enabledFeatures & Features.OverrideBakedGI) != 0) + AddProperty(Styles.overrideBakedGI, () => lightingData.overrideBakedGI, (newValue) => lightingData.overrideBakedGI = newValue); } - AddProperty(Styles.supportLodCrossFade, () => builtinData.supportLodCrossFade, (newValue) => builtinData.supportLodCrossFade = newValue); - AddProperty(addPrecomputedVelocityText, () => builtinData.addPrecomputedVelocity, (newValue) => builtinData.addPrecomputedVelocity = newValue); + if ((enabledFeatures & Features.LodCrossfade) != 0) + AddProperty(Styles.supportLodCrossFade, () => builtinData.supportLodCrossFade, (newValue) => builtinData.supportLodCrossFade = newValue); + if ((enabledFeatures & Features.PrecomputedVelocity) != 0) + AddProperty(addPrecomputedVelocityText, () => builtinData.addPrecomputedVelocity, (newValue) => builtinData.addPrecomputedVelocity = newValue); - if (Unsupported.IsDeveloperMode()) + if (Unsupported.IsDeveloperMode() && (enabledFeatures & Features.DebugSymbols) != 0) AddProperty(Styles.debugSymbolsText, () => systemData.debugSymbols, (newValue) => systemData.debugSymbols = newValue); } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/HDSubTarget.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/HDSubTarget.cs index 02c1f8fbab2..64bb92c72ea 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/HDSubTarget.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/ShaderGraph/HDSubTarget.cs @@ -78,7 +78,7 @@ public virtual ScriptableObject GetMetadataObject(GraphDataReadOnly graph) hdMetadata.migrateFromOldCrossPipelineSG = m_MigrateFromOldCrossPipelineSG; hdMetadata.hdSubTargetVersion = systemData.version; hdMetadata.hasVertexModificationInMotionVector = systemData.customVelocity || systemData.tessellation || graph.AnyVertexAnimationActive(); - hdMetadata.isVFXCompatible = graph.IsVFXCompatible(); + hdMetadata.isVFXCompatible = target.SupportsVFX(); return hdMetadata; } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitData.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitData.cs index 886bbdf9a69..7affab0c3eb 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitData.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitData.cs @@ -4,22 +4,6 @@ namespace UnityEditor.Rendering.HighDefinition.ShaderGraph { class TerrainLitData : HDTargetData { - [SerializeField] - private bool m_EnableHeightBlened; - public bool enableHeightBlend - { - get => m_EnableHeightBlened; - set => m_EnableHeightBlened = value; - } - - [SerializeField] - private float m_HeightTransition; - public float heightTransition - { - get => m_HeightTransition; - set => m_HeightTransition = value; - } - [SerializeField] private bool m_EnableInstancedPerPixelNormal = true; public bool enableInstancedPerPixelNormal diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitSubTarget.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitSubTarget.cs index bef6e6918eb..a569ae0bfbb 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitSubTarget.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/TerrainLit/ShaderGraph/TerrainLitSubTarget.cs @@ -117,7 +117,6 @@ protected override void CollectPassKeywords(ref PassDescriptor pass) pass.keywords.Add(TerrainKeywordDescriptors.TerrainMaskmap); pass.keywords.Add(TerrainKeywordDescriptors.Terrain8Layers); pass.keywords.Add(TerrainKeywordDescriptors.TerrainSpecularOcclusionNone); - pass.keywords.Add(TerrainKeywordDescriptors.TerrainBlendHeight); pass.keywords.Add(CoreKeywordDescriptors.DepthOffset, new FieldCondition(HDFields.DepthOffset, true)); pass.keywords.Add(CoreKeywordDescriptors.ConservativeDepthOffset, new FieldCondition(HDFields.ConservativeDepthOffset, true)); @@ -236,27 +235,6 @@ public override void GetActiveBlocks(ref TargetActiveBlockContext context) public override void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode) { - collector.AddShaderProperty(new BooleanShaderProperty - { - value = terrainLitData.enableHeightBlend, - hidden = true, - overrideHLSLDeclaration = true, - hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare, - overrideReferenceName = "_EnableHeightBlend", - displayName = "Enable Height Blend", - }); - - collector.AddShaderProperty(new Vector1ShaderProperty - { - floatType = FloatType.Slider, - value = terrainLitData.heightTransition, - hidden = false, - overrideHLSLDeclaration = true, - hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare, - overrideReferenceName = "_HeightTransition", - displayName = "Height Transition", - }); - collector.AddShaderProperty(new BooleanShaderProperty { value = terrainLitData.enableInstancedPerPixelNormal, @@ -297,6 +275,16 @@ public override void CollectShaderProperties(PropertyCollector collector, Genera displayName = "DefaultWhiteTex", }); + collector.AddShaderProperty(new Texture2DShaderProperty + { + defaultType = Texture2DShaderProperty.DefaultType.Black, + hidden = true, + overrideHLSLDeclaration = true, + hlslDeclarationOverride = HLSLDeclaration.DoNotDeclare, + overrideReferenceName = "_BlackTex", + displayName = "DefaultBlackTex", + }); + collector.AddShaderProperty(new ColorShaderProperty() { value = new Color(1.0f, 1.0f, 1.0f, 1.0f), @@ -432,7 +420,10 @@ public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, Acti protected override void AddInspectorPropertyBlocks(SubTargetPropertiesGUI blockList) { blockList.AddPropertyBlock(new TerrainLitSurfaceOptionPropertyBlock(SurfaceOptionPropertyBlock.Features.Lit)); - blockList.AddPropertyBlock(new AdvancedOptionsPropertyBlock()); + var disabledAdvancedFeatures = AdvancedOptionsPropertyBlock.Features.LodCrossfade + | AdvancedOptionsPropertyBlock.Features.PrecomputedVelocity + | AdvancedOptionsPropertyBlock.Features.DebugSymbols; + blockList.AddPropertyBlock(new AdvancedOptionsPropertyBlock(AdvancedOptionsPropertyBlock.Features.All ^ disabledAdvancedFeatures)); } @@ -454,6 +445,23 @@ static class HDTerrainStructs } }; + public static StructDescriptor MetaAttributesMesh = new StructDescriptor() + { + name = "AttributesMesh", + packFields = false, + fields = new FieldDescriptor[] + { + HDStructFields.AttributesMesh.positionOS, + HDStructFields.AttributesMesh.normalOS, + HDStructFields.AttributesMesh.uv0, + HDStructFields.AttributesMesh.uv1, + HDStructFields.AttributesMesh.uv2, + HDStructFields.AttributesMesh.color, + HDStructFields.AttributesMesh.instanceID, + HDStructFields.AttributesMesh.vertexID, + } + }; + public static StructDescriptor VaryingsMeshToPS = new StructDescriptor() { name = "VaryingsMeshToPS", @@ -484,6 +492,14 @@ static class TerrainStructCollections { Structs.VertexDescriptionInputs }, { Structs.SurfaceDescriptionInputs }, }; + + public static StructCollection Meta = new StructCollection + { + { HDTerrainStructs.MetaAttributesMesh }, + { HDTerrainStructs.VaryingsMeshToPS }, + { Structs.VertexDescriptionInputs }, + { Structs.SurfaceDescriptionInputs }, + }; } #endregion @@ -552,15 +568,6 @@ static class TerrainKeywordDescriptors scope = KeywordScope.Local, }; - public static KeywordDescriptor TerrainBlendHeight = new KeywordDescriptor() - { - displayName = "Terrain Blend Height", - referenceName = "_TERRAIN_BLEND_HEIGHT", - type = KeywordType.Boolean, - definition = KeywordDefinition.ShaderFeature, - scope = KeywordScope.Local, - }; - public static KeywordDescriptor TerrainBaseMapGen = new KeywordDescriptor() { displayName = "Terrain Base Map Generation", @@ -746,7 +753,7 @@ IncludeCollection GenerateIncludes() public static PassDescriptor GenerateMETA(bool supportLighting) { - return new PassDescriptor + var pass = new PassDescriptor { // Definition displayName = "META", @@ -758,7 +765,7 @@ public static PassDescriptor GenerateMETA(bool supportLighting) validVertexBlocks = new BlockFieldDescriptor[0], // Collections - structs = GenerateStructs(), + structs = new StructCollection { TerrainStructCollections.Meta }, requiredFields = CoreRequiredFields.Meta, renderStates = CoreRenderStates.Meta, // Note: no tessellation for meta pass @@ -767,6 +774,7 @@ public static PassDescriptor GenerateMETA(bool supportLighting) keywords = new KeywordCollection() { CoreKeywordDescriptors.EditorVisualization, HDTerrainPasses.AlphaTestOn, }, includes = GenerateIncludes(), }; + return pass; IncludeCollection GenerateIncludes() { diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/UIBlocks/TerrainSurfaceOptionsUIBlock.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/UIBlocks/TerrainSurfaceOptionsUIBlock.cs index 0c11f91ad27..cb374e31850 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/UIBlocks/TerrainSurfaceOptionsUIBlock.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/UIBlocks/TerrainSurfaceOptionsUIBlock.cs @@ -26,8 +26,6 @@ internal enum HeightParametrization internal class Styles { public static GUIContent optionText { get; } = EditorGUIUtility.TrTextContent("Terrain Options"); - public static readonly GUIContent enableHeightBlend = new GUIContent("Enable Height-based Blend", "Blend terrain layers based on height values."); - public static readonly GUIContent heightTransition = new GUIContent("Height Transition", "Size in world units of the smooth transition between layers."); public static readonly GUIContent enableInstancedPerPixelNormal = new GUIContent("Enable Per-pixel Normal", "Enable per-pixel normal when the terrain uses instanced rendering."); public static readonly GUIContent diffuseTexture = new GUIContent("Diffuse"); @@ -51,8 +49,6 @@ internal class Styles public static readonly GUIContent smoothness = new GUIContent("A: Smoothness"); } - private MaterialProperty enableHeightBlend = null; - private MaterialProperty heightTransition = null; private MaterialProperty enableInstancedPerPixelNormal = null; private bool m_ShowChannelRemapping = false; @@ -80,17 +76,6 @@ public override void LoadMaterialProperties() ///
protected override void OnGUIOpen() { - if (enableHeightBlend != null) - { - materialEditor.ShaderProperty(enableHeightBlend, Styles.enableHeightBlend); - if (enableHeightBlend.floatValue > 0) - { - EditorGUI.indentLevel++; - materialEditor.ShaderProperty(heightTransition, Styles.heightTransition); - EditorGUI.indentLevel--; - } - } - if (enableInstancedPerPixelNormal != null) { EditorGUI.BeginDisabledGroup(!materialEditor.IsInstancingEnabled()); @@ -298,11 +283,7 @@ protected void FindTerrainLitProperties(MaterialProperty[] props) { foreach (var prop in props) { - if (prop.name == HDMaterialProperties.kEnableHeightBlend) - enableHeightBlend = prop; - else if (prop.name == HDMaterialProperties.kHeightTransition) - heightTransition = prop; - else if (prop.name == HDMaterialProperties.kEnableInstancedPerPixelNormal) + if (prop.name == HDMaterialProperties.kEnableInstancedPerPixelNormal) enableInstancedPerPixelNormal = prop; } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/Water/ShaderGraph/CreateWaterShaderGraph.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/Water/ShaderGraph/CreateWaterShaderGraph.cs index 5d0737a9c4c..6bd69dd27fc 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/Material/Water/ShaderGraph/CreateWaterShaderGraph.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/Material/Water/ShaderGraph/CreateWaterShaderGraph.cs @@ -48,7 +48,7 @@ public override void Action(int instanceId, string pathName, string resourceFile [MenuItem("Assets/Create/Shader Graph/HDRP/Water Shader Graph", priority = CoreUtils.Priorities.assetsCreateShaderMenuPriority + 6)] static void CreateWaterGraphCopy() { - ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "Water Shader Graph.shadergraph", null, null); + ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "Water Shader Graph.shadergraph", ShaderGraphImporter.GetIcon(), null); } } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/CustomPass/CustomPassVolumeGizmoDrawer.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/CustomPass/CustomPassVolumeGizmoDrawer.cs index c23b100fcf2..f0f504b3efb 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/CustomPass/CustomPassVolumeGizmoDrawer.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/CustomPass/CustomPassVolumeGizmoDrawer.cs @@ -5,35 +5,56 @@ namespace UnityEditor.Rendering { - class CustomVolumePassGizmoDrawer : IVolumeAdditionalGizmo + class CustomVolumePassGizmoDrawer { - public Type type => typeof(CustomPassVolume); - - public void OnBoxColliderDraw(IVolume scr, BoxCollider c) + [DrawGizmo(GizmoType.Active | GizmoType.Selected | GizmoType.NonSelected)] + static void OnDrawGizmos(CustomPassVolume volume, GizmoType gizmoType) { - var customPass = scr as CustomPassVolume; - if (customPass.fadeRadius > 0) + if (!volume.enabled || volume.isGlobal || volume.colliders == null) + return; + + Gizmos.color = VolumesPreferences.volumeGizmoColor; + + foreach (var collider in volume.colliders) { - var twiceFadeRadius = customPass.fadeRadius * 2; - // invert te scale for the fade radius because it's in fixed units - Vector3 s = new Vector3( - twiceFadeRadius / customPass.transform.localScale.x, - twiceFadeRadius / customPass.transform.localScale.y, - twiceFadeRadius / customPass.transform.localScale.z - ); - Gizmos.DrawWireCube(c.center, c.size + s); + if (!collider || !collider.enabled) + continue; + + bool fadeRadiusEnabled = volume.fadeRadius > 0f; + switch (collider) + { + case BoxCollider c: + VolumeGizmoDrawer.DrawBoxCollider(c.transform, c.center, c.size); + if (fadeRadiusEnabled) + DrawFadeRadiusBox(volume, c); + break; + case SphereCollider c: + VolumeGizmoDrawer.DrawSphereCollider(c.transform, c.center, c.radius); + if (fadeRadiusEnabled) + DrawFadeRadiusSphere(volume, c); + break; + case MeshCollider c: + VolumeGizmoDrawer.DrawMeshCollider(c.transform, c.sharedMesh); + break; + } } } - public void OnMeshColliderDraw(IVolume scr, MeshCollider c) + public static void DrawFadeRadiusBox(CustomPassVolume volume, BoxCollider c) { + var twiceFadeRadius = volume.fadeRadius * 2; + // invert te scale for the fade radius because it's in fixed units + Vector3 s = new Vector3( + twiceFadeRadius / volume.transform.localScale.x, + twiceFadeRadius / volume.transform.localScale.y, + twiceFadeRadius / volume.transform.localScale.z + ); + Gizmos.DrawWireCube(c.center, c.size + s); } - public void OnSphereColliderDraw(IVolume scr, SphereCollider c) + public static void DrawFadeRadiusSphere(CustomPassVolume volume, SphereCollider c) { - var customPass = scr as CustomPassVolume; - if (customPass.fadeRadius > 0) - Gizmos.DrawWireSphere(c.center, c.radius + customPass.fadeRadius / customPass.transform.lossyScale.x); + Gizmos.DrawWireSphere(c.center, c.radius + volume.fadeRadius / volume.transform.lossyScale.x); } } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDAssetFactory.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDAssetFactory.cs index 9007ae997e1..afee8048564 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDAssetFactory.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDAssetFactory.cs @@ -24,7 +24,7 @@ public override void Action(int instanceId, string pathName, string resourceFile [MenuItem("Assets/Create/Rendering/HDRP Asset", priority = CoreUtils.Sections.section1 + CoreUtils.Priorities.assetsCreateRenderingMenuPriority)] static void CreateHDRenderPipeline() { - var icon = EditorGUIUtility.FindTexture("ScriptableObject Icon"); + var icon = CoreUtils.GetIconForType(); ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "New HDRenderPipelineAsset.asset", icon, null); } } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineEditor.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineEditor.cs index 6a02db0d08c..c2778b5f70a 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineEditor.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineEditor.cs @@ -18,9 +18,24 @@ sealed class HDRenderPipelineEditor : Editor internal ReorderableList reusableReorderableList; +#if ENABLE_UPSCALER_FRAMEWORK + internal UpscalerOptionsEditorCache upscalerOptionsEditorCache => m_UpscalerOptionsEditorCache; + UpscalerOptionsEditorCache m_UpscalerOptionsEditorCache; +#endif + void OnEnable() { m_SerializedHDRenderPipeline = new SerializedHDRenderPipelineAsset(serializedObject); +#if ENABLE_UPSCALER_FRAMEWORK + m_UpscalerOptionsEditorCache = new UpscalerOptionsEditorCache(); +#endif + } + + void OnDisable() + { +#if ENABLE_UPSCALER_FRAMEWORK + m_UpscalerOptionsEditorCache?.Cleanup(); +#endif } public override void OnInspectorGUI() diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineMenuItems.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineMenuItems.cs index eff68c0dfd4..91ff18191f2 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineMenuItems.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineMenuItems.cs @@ -259,7 +259,7 @@ protected override void PostCreateAssetWork(DiffusionProfileSettings asset) [MenuItem("Assets/Create/Rendering/HDRP Diffusion Profile", priority = CoreUtils.Sections.section4 + CoreUtils.Priorities.assetsCreateRenderingMenuPriority)] static void MenuCreateDiffusionProfile() { - var icon = EditorGUIUtility.FindTexture("ScriptableObject Icon"); + var icon = CoreUtils.GetIconForType(); ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance(), "New Diffusion Profile.asset", icon, null); } diff --git a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs index a6bace5bf55..75cc2e4d7b6 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs @@ -893,9 +893,10 @@ uint GUIIndexToPresetValue(uint presetBitmask, uint index) ++EditorGUI.indentLevel; bool optionsChanged = false; + HDRenderPipelineEditor hdrpEditor = owner as HDRenderPipelineEditor; // O(N^2) IUpscaler name comparison but typical use case is <6 entries w/ each entry around 20B of char data - for(int i=0; i< serialized.renderPipelineSettings.dynamicResolutionSettings.advancedUpscalerNames.arraySize; ++i) + for (int i = 0; i < serialized.renderPipelineSettings.dynamicResolutionSettings.advancedUpscalerNames.arraySize; ++i) { string advancedUpscalerName = serialized.renderPipelineSettings.dynamicResolutionSettings.advancedUpscalerNames.GetArrayElementAtIndex(i).stringValue; @@ -910,31 +911,40 @@ uint GUIIndexToPresetValue(uint presetBitmask, uint index) EditorGUILayout.LabelField(upscalerOptions.UpscalerName, EditorStyles.boldLabel); ++EditorGUI.indentLevel; - // injection point selection (common to all IUpscalers) - int injectionPointBefore = (int)upscalerOptions.InjectionPoint; - int injectionPointSelection = EditorGUILayout.IntPopup(Styles.IUpscalerInjectionPoint, (int)upscalerOptions.InjectionPoint, Styles.UpscalerInjectionPointNames, Styles.UpscalerInjectionPointValues); - bool injectionPointChanged = injectionPointBefore != injectionPointSelection; - if (injectionPointChanged) - upscalerOptions.InjectionPoint = (DynamicResolutionHandler.UpsamplerScheduleType)injectionPointSelection; - - // draw rest of the options - bool optionsChangedOnThisUpscaler = upscalerOptions.DrawOptionsEditorGUI(); + Debug.Assert(hdrpEditor != null); + Editor editor = hdrpEditor.upscalerOptionsEditorCache.GetOrCreateEditor(upscalerOptions); + if (editor != null) + { + //------------------------------------------------------------------------------------------------------------------------ + // Edge Case: Manually draw the "InjectionPoint" property. + // It is hidden by default to serve URP requirements. + // When URP supports injection points, this snippet can be safely removed + SerializedProperty injectionPointProp = editor.serializedObject.FindProperty("m_InjectionPoint"); + if (injectionPointProp != null) + { + EditorGUILayout.PropertyField(injectionPointProp); + editor.serializedObject.ApplyModifiedProperties(); + } + //------------------------------------------------------------------------------------------------------------------------ - optionsChanged = optionsChanged || injectionPointChanged || optionsChangedOnThisUpscaler; + editor.OnInspectorGUI(); + } --EditorGUI.indentLevel; - } - } + + } // foreach upscaler option + } // foreach advanced upscaler name --EditorGUI.indentLevel; - if(optionsChanged) + if (optionsChanged) { EditorUtility.SetDirty(HDRenderPipeline.currentAsset); } } #endif #endregion + EditorGUILayout.PropertyField(serialized.renderPipelineSettings.dynamicResolutionSettings.dynamicResType, Styles.dynResType); bool isHwDrs = (serialized.renderPipelineSettings.dynamicResolutionSettings.dynamicResType.intValue == (int)DynamicResolutionType.Hardware); bool gfxDeviceSupportsHwDrs = HDUtils.IsHardwareDynamicResolutionSupportedByDevice(SystemInfo.graphicsDeviceType); diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/UberPost.compute b/Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/UberPost.compute index 8ada7553cd7..32a14248c3e 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/UberPost.compute +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/UberPost.compute @@ -164,6 +164,27 @@ void Uber(uint3 dispatchThreadId : SV_DispatchThreadID) inputColor = color; #endif +#if defined(SHADEROPTIONS_PRE_EXPOSITION) + // Before applying color grading, we correct the exposition of the frame. In HDRP we use the pre-exposure to calculate + // intermediate color of the frame so that the values fits in 111110 buffers. Because pre-exposure needs to be applied from the start + // we need to use the value from previous frame, this introduces an error that can be corrected after the dynamic exposure pass. + // We do this correction in the uber post pass to avoid adding another extra fullscreen pass. + + // We're after the dynamic/fixed exposure pass, so the current exposure value is stored in the previous texture and vice-versa. + float previousFrameExposure = GetCurrentExposureMultiplier(); + float currentFrameExposure = GetPreviousExposureMultiplier(); + // When the HDCamera is reset, post-processing history and exposure resets as well, leaving the default value for the current + // previous exposure frame even though the current exposure is still be valid (the frame was pre-exposed with a non default value). + // To avoid this case we don't perform exposure compensation if the current frame exposure is the default value. + if (currentFrameExposure != 1) + { + // Find the original color of the pixel as if it was not exposed, hence removing the previous frame exposure from current frame. + float3 nonExposedColor = color.rgb / previousFrameExposure; + // Apply the current frame exposure to get up to date luminance. + color.rgb = nonExposedColor * currentFrameExposure; + } +#endif + // Bloom UNITY_BRANCH if (BloomEnabled) diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.PostProcess.cs b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.PostProcess.cs index 8dcfc7ef7e4..2c019d70e5c 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.PostProcess.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.PostProcess.cs @@ -1608,6 +1608,11 @@ static void DoDynamicExposure(DynamicExposureData data, CommandBuffer cmd) cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._InputTexture, data.tmpTarget32); cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._OutputTexture, data.nextExposure); cmd.DispatchCompute(cs, kernel, 1, 1, 1); + + // After computing the exposure of the current frame, we can update the global variable + // so that the exposure compensation in the uber post takes the correct value instead + // of the default during the first frame. + cmd.SetGlobalTexture(HDShaderIDs._PreviousExposureTexture, data.nextExposure); } static void DoHistogramBasedExposure(DynamicExposureData data, CommandBuffer cmd) @@ -1660,6 +1665,11 @@ static void DoHistogramBasedExposure(DynamicExposureData data, CommandBuffer cmd } cmd.DispatchCompute(cs, kernel, 1, 1, 1); + + // After computing the exposure of the current frame, we can update the global variable + // so that the exposure compensation in the uber post takes the correct value instead + // of the default during the first frame. + cmd.SetGlobalTexture(HDShaderIDs._PreviousExposureTexture, data.nextExposure); } class DynamicExposureData diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.RenderGraph.cs b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.RenderGraph.cs index 85a78cfe49a..acd55c5f569 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.RenderGraph.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.RenderGraph.cs @@ -146,6 +146,8 @@ void RecordRenderGraph(RenderRequest renderRequest, } else if (hdCamera.frameSettings.IsEnabled(FrameSettingsField.RayTracing) && pathTracing.enable.value && hdCamera.camera.cameraType != CameraType.Preview && GetRayTracingState() && GetRayTracingClusterState()) { + m_TileAndClusterData.listsAreInitialized = false; + // We only request the light cluster if we are gonna use it for debug mode if (FullScreenDebugMode.LightCluster == m_CurrentDebugDisplaySettings.data.fullScreenDebugMode && GetRayTracingClusterState()) { diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipelineAsset.cs b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipelineAsset.cs index af921d813a8..27c01bfbd55 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipelineAsset.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipelineAsset.cs @@ -18,6 +18,7 @@ namespace UnityEngine.Rendering.HighDefinition /// High Definition Render Pipeline asset. ///
[HDRPHelpURLAttribute("HDRP-Asset")] + [Icon("UnityEngine/Rendering/RenderPipelineAsset Icon")] #if UNITY_EDITOR // [ShaderKeywordFilter.ApplyRulesIfTagsEqual("RenderPipeline", "HDRenderPipeline")] #endif diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassVolume.cs b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassVolume.cs index ac0d531b784..087f51ef4cc 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassVolume.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassVolume.cs @@ -18,7 +18,7 @@ namespace UnityEngine.Rendering.HighDefinition ///
[ExecuteAlways] [HDRPHelpURLAttribute("Custom-Pass")] - public class CustomPassVolume : MonoBehaviour, IVolume + public class CustomPassVolume : MonoBehaviour { [SerializeField, FormerlySerializedAs("isGlobal")] private bool m_IsGlobal = true; diff --git a/Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/SkyManager.cs b/Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/SkyManager.cs index db0efef880f..f6305dad3a9 100644 --- a/Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/SkyManager.cs +++ b/Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/SkyManager.cs @@ -1260,6 +1260,12 @@ public void UpdateEnvironment(RenderGraph renderGraph, HDCamera hdCamera, Light UpdateEnvironment(renderGraph, hdCamera, hdCamera.lightingSky, sunLight, m_UpdateRequired, ambientMode == SkyAmbientMode.Dynamic, false, ambientMode); + // Also update the visual sky context if it's different from lighting sky + if (hdCamera.visualSky != hdCamera.lightingSky) + { + UpdateEnvironment(renderGraph, hdCamera, hdCamera.visualSky, sunLight, m_UpdateRequired, false, false, ambientMode); + } + // Preview camera will have a different sun, therefore the hash for the static lighting sky will change and force a recomputation // because we only maintain one static sky. Since we don't care that the static lighting may be a bit different in the preview we never recompute // and we use the one from the main camera. diff --git a/Packages/com.unity.render-pipelines.high-definition/package.json b/Packages/com.unity.render-pipelines.high-definition/package.json index a98a9fa220f..7d81c371355 100644 --- a/Packages/com.unity.render-pipelines.high-definition/package.json +++ b/Packages/com.unity.render-pipelines.high-definition/package.json @@ -1,17 +1,18 @@ { "name": "com.unity.render-pipelines.high-definition", "description": "The High Definition Render Pipeline (HDRP) is a high-fidelity Scriptable Render Pipeline built by Unity to target modern (Compute Shader compatible) platforms. HDRP utilizes Physically-Based Lighting techniques, linear lighting, HDR lighting, and a configurable hybrid Tile/Cluster deferred/Forward lighting architecture and gives you the tools you need to create games, technical demos, animations, and more to a high graphical standard.", - "version": "17.3.0", - "unity": "6000.3", + "version": "17.4.0", + "unity": "6000.4", "displayName": "High Definition Render Pipeline", "dependencies": { "com.unity.modules.video": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", - "com.unity.render-pipelines.core": "17.3.0", - "com.unity.shadergraph": "17.3.0", - "com.unity.visualeffectgraph": "17.3.0", - "com.unity.render-pipelines.high-definition-config": "17.3.0" + "com.unity.modules.physics": "1.0.0", + "com.unity.render-pipelines.core": "17.4.0", + "com.unity.shadergraph": "17.4.0", + "com.unity.visualeffectgraph": "17.4.0", + "com.unity.render-pipelines.high-definition-config": "17.4.0" }, "keywords": [ "graphics", diff --git a/Packages/com.unity.render-pipelines.universal-config/CHANGELOG.md b/Packages/com.unity.render-pipelines.universal-config/CHANGELOG.md index 1ef39296e7e..cfe17d2c2b0 100644 --- a/Packages/com.unity.render-pipelines.universal-config/CHANGELOG.md +++ b/Packages/com.unity.render-pipelines.universal-config/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Version Updated The version number for this package has increased due to a version update of a related graphics package. +## [17.0.3] - 2025-08-27 + +This version is compatible with Unity 6000.3.0b1. +For the release notes, refer to the [Unity download archive](https://unity.com/releases/editor/archive). + ## [17.0.1] - 2023-12-21 This version is compatible with Unity 2023.3.0b2. diff --git a/Packages/com.unity.render-pipelines.universal-config/package.json b/Packages/com.unity.render-pipelines.universal-config/package.json index d674f473a1f..0e3557f132e 100644 --- a/Packages/com.unity.render-pipelines.universal-config/package.json +++ b/Packages/com.unity.render-pipelines.universal-config/package.json @@ -1,10 +1,10 @@ { "name": "com.unity.render-pipelines.universal-config", "description": "Configuration files for the Universal Render Pipeline.", - "version": "17.0.3", - "unity": "6000.0", + "version": "17.4.0", + "unity": "6000.4", "displayName": "Universal Render Pipeline Config", "dependencies": { - "com.unity.render-pipelines.core": "17.0.3" + "com.unity.render-pipelines.core": "17.4.0" } } \ No newline at end of file diff --git a/Packages/com.unity.render-pipelines.universal/CHANGELOG.md b/Packages/com.unity.render-pipelines.universal/CHANGELOG.md index 94e93bafdf4..5f84bfc0c77 100644 --- a/Packages/com.unity.render-pipelines.universal/CHANGELOG.md +++ b/Packages/com.unity.render-pipelines.universal/CHANGELOG.md @@ -10,6 +10,11 @@ uid: urp-changelog Version Updated The version number for this package has increased due to a version update of a related graphics package. +## [17.3.0] - 2025-08-27 + +This version is compatible with Unity 6000.3.0b1. +For the release notes, refer to the [Unity download archive](https://unity.com/releases/editor/archive). + ## [17.2.0] - 2025-05-14 This version is compatible with Unity 6000.2.0b2. diff --git a/Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/LayerBatch.uxml b/Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/LayerBatch.uxml index f98b2d74e4a..afd4b895274 100644 --- a/Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/LayerBatch.uxml +++ b/Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/LayerBatch.uxml @@ -1,5 +1,5 @@ -