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