A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.Collections.Generic;
4using System.Reflection;
5using UnityEngine.Profiling;
6using UnityEngine.SceneManagement;
7using Chunk = UnityEngine.Rendering.ProbeBrickPool.BrickChunkAlloc;
8using Brick = UnityEngine.Rendering.ProbeBrickIndex.Brick;
9using Unity.Collections;
10using Unity.Profiling;
11using Unity.Mathematics;
12using UnityEngine.Experimental.Rendering;
13
14#if UNITY_EDITOR
15using System.Linq.Expressions;
16using UnityEditor;
17#endif
18
19namespace UnityEngine.Rendering
20{
21 internal static class SceneExtensions
22 {
23 static PropertyInfo s_SceneGUID = typeof(Scene).GetProperty("guid", BindingFlags.NonPublic | BindingFlags.Instance);
24 public static string GetGUID(this Scene scene)
25 {
26 Debug.Assert(s_SceneGUID != null, "Reflection for scene GUID failed");
27 return (string)s_SceneGUID.GetValue(scene);
28 }
29 }
30
31 /// <summary>
32 /// Initialization parameters for the probe volume system.
33 /// </summary>
34 public struct ProbeVolumeSystemParameters
35 {
36 /// <summary>
37 /// The memory budget determining the size of the textures containing SH data.
38 /// </summary>
39 public ProbeVolumeTextureMemoryBudget memoryBudget;
40 /// <summary>
41 /// The memory budget determining the size of the textures used for blending between scenarios.
42 /// </summary>
43 public ProbeVolumeBlendingTextureMemoryBudget blendingMemoryBudget;
44 /// <summary>
45 /// The <see cref="ProbeVolumeSHBands"/>
46 /// </summary>
47 public ProbeVolumeSHBands shBands;
48 /// <summary>True if APV should support lighting scenarios.</summary>
49 public bool supportScenarios;
50 /// <summary>True if APV should support lighting scenario blending.</summary>
51 public bool supportScenarioBlending;
52 /// <summary>True if APV should support streaming of cell data to the GPU.</summary>
53 public bool supportGPUStreaming;
54 /// <summary>True if APV should support streaming of cell data from the disk.</summary>
55 public bool supportDiskStreaming;
56
57 /// <summary>
58 /// The shader used to visualize the probes in the debug view.
59 /// </summary>
60 [Obsolete("This field is not used anymore.")]
61 public Shader probeDebugShader;
62 /// <summary>
63 /// The shader used to visualize the way probes are sampled for a single pixel in the debug view.
64 /// </summary>
65 [Obsolete("This field is not used anymore.")]
66 public Shader probeSamplingDebugShader;
67 /// <summary>
68 /// The debug texture used to display probe weight in the debug view.
69 /// </summary>
70 [Obsolete("This field is not used anymore.")]
71 public Texture probeSamplingDebugTexture;
72 /// <summary>
73 /// The debug mesh used to visualize the way probes are sampled for a single pixel in the debug view.
74 /// </summary>
75 [Obsolete("This field is not used anymore.")]
76 public Mesh probeSamplingDebugMesh;
77 /// <summary>
78 /// The shader used to visualize probes virtual offset in the debug view.
79 /// </summary>
80 [Obsolete("This field is not used anymore.")]
81 public Shader offsetDebugShader;
82 /// <summary>
83 /// The shader used to visualize APV fragmentation.
84 /// </summary>
85 [Obsolete("This field is not used anymore.")]
86 public Shader fragmentationDebugShader;
87 /// <summary>
88 /// The compute shader used to interpolate between two lighting scenarios.
89 /// Set to null if blending is not supported.
90 /// </summary>
91 [Obsolete("This field is not used anymore.")]
92 public ComputeShader scenarioBlendingShader;
93 /// <summary>
94 /// The compute shader used to upload streamed data to the GPU.
95 /// </summary>
96 [Obsolete("This field is not used anymore.")]
97 public ComputeShader streamingUploadShader;
98
99 /// <summary>
100 /// The <see cref="ProbeVolumeSceneData"/>
101 /// </summary>
102 [Obsolete("This field is not used anymore.")]
103 public ProbeVolumeSceneData sceneData;
104 /// <summary>True if APV is able to show runtime debug information.</summary>
105 [Obsolete("This field is not used anymore. Used with the current Shader Stripping Settings. #from(2023.3)")]
106 public bool supportsRuntimeDebug;
107 }
108
109 internal struct ProbeVolumeShadingParameters
110 {
111 public float normalBias;
112 public float viewBias;
113 public bool scaleBiasByMinDistanceBetweenProbes;
114 public float samplingNoise;
115 public float weight;
116 public APVLeakReductionMode leakReductionMode;
117 public int frameIndexForNoise;
118 public float reflNormalizationLowerClamp;
119 public float reflNormalizationUpperClamp;
120 public float skyOcclusionIntensity;
121 public bool skyOcclusionShadingDirection;
122 public int regionCount;
123 public uint4 regionLayerMasks;
124 public Vector3 worldOffset;
125 }
126
127 /// <summary>
128 /// Possible values for the probe volume memory budget (determines the size of the textures used).
129 /// </summary>
130 [Serializable]
131 public enum ProbeVolumeTextureMemoryBudget
132 {
133 /// <summary>Low Budget</summary>
134 MemoryBudgetLow = 512,
135 /// <summary>Medium Budget</summary>
136 MemoryBudgetMedium = 1024,
137 /// <summary>High Budget</summary>
138 MemoryBudgetHigh = 2048,
139 }
140
141 /// <summary>
142 /// Possible values for the probe volume scenario blending memory budget (determines the size of the textures used).
143 /// </summary>
144 [Serializable]
145 public enum ProbeVolumeBlendingTextureMemoryBudget
146 {
147 /// <summary>Low Budget</summary>
148 MemoryBudgetLow = 128,
149 /// <summary>Medium Budget</summary>
150 MemoryBudgetMedium = 256,
151 /// <summary>High Budget</summary>
152 MemoryBudgetHigh = 512,
153 }
154
155 /// <summary>
156 /// Number of Spherical Harmonics bands that are used with Probe Volumes
157 /// </summary>
158 [Serializable]
159 public enum ProbeVolumeSHBands
160 {
161 /// <summary>Up to the L1 band of Spherical Harmonics</summary>
162 SphericalHarmonicsL1 = 1,
163 /// <summary>Up to the L2 band of Spherical Harmonics</summary>
164 SphericalHarmonicsL2 = 2,
165 }
166
167 /// <summary>
168 /// The reference volume for the Adaptive Probe Volumes system. This defines the structure in which volume assets are loaded into. There must be only one, hence why it follow a singleton pattern.
169 /// </summary>
170 public partial class ProbeReferenceVolume
171 {
172 [Serializable]
173 internal struct IndirectionEntryInfo
174 {
175 public Vector3Int positionInBricks;
176 public int minSubdiv;
177 public Vector3Int minBrickPos;
178 public Vector3Int maxBrickPosPlusOne;
179 public bool hasMinMax; // should be removed, only kept for migration
180 public bool hasOnlyBiggerBricks; // True if it has only bricks that are bigger than the entry itself
181 }
182
183 [Serializable]
184 internal class CellDesc
185 {
186 public Vector3Int position;
187 public int index;
188 public int probeCount;
189 public int minSubdiv;
190 public int indexChunkCount;
191 public int shChunkCount;
192 public int bricksCount;
193
194 // This is data that is generated at bake time to not having to re-analyzing the content of the cell for the indirection buffer.
195 // This is not technically part of the descriptor of the cell but it needs to be here because it's computed at bake time and needs
196 // to be serialized with the rest of the cell.
197 public IndirectionEntryInfo[] indirectionEntryInfo;
198
199 public override string ToString()
200 {
201 return $"Index = {index} position = {position}";
202 }
203 }
204
205 internal class CellData
206 {
207 // Shared Data
208 public NativeArray<byte> validityNeighMaskData;
209 public NativeArray<ushort> skyOcclusionDataL0L1 { get; internal set; }
210 public NativeArray<byte> skyShadingDirectionIndices { get; internal set; }
211
212
213 // Scenario Data
214 public struct PerScenarioData
215 {
216 // L0/L1 Data
217 public NativeArray<ushort> shL0L1RxData;
218 public NativeArray<byte> shL1GL1RyData;
219 public NativeArray<byte> shL1BL1RzData;
220
221 // Optional L2 Data
222 public NativeArray<byte> shL2Data_0;
223 public NativeArray<byte> shL2Data_1;
224 public NativeArray<byte> shL2Data_2;
225 public NativeArray<byte> shL2Data_3;
226
227 // 4 unorm per probe, 1 for each occluded light
228 public NativeArray<byte> probeOcclusion;
229 }
230
231 public Dictionary<string, PerScenarioData> scenarios = new Dictionary<string, PerScenarioData>();
232
233 // Brick data.
234 public NativeArray<Brick> bricks { get; internal set; }
235
236 // Support Data
237 public NativeArray<Vector3> probePositions { get; internal set; }
238 public NativeArray<float> touchupVolumeInteraction { get; internal set; } // Only used by a specific debug view.
239 public NativeArray<Vector3> offsetVectors { get; internal set; }
240 public NativeArray<float> validity { get; internal set; }
241 public NativeArray<byte> layer { get; internal set; } // Only used by a specific debug view.
242
243 public void CleanupPerScenarioData(in PerScenarioData data)
244 {
245 if (data.shL0L1RxData.IsCreated)
246 {
247 data.shL0L1RxData.Dispose();
248 data.shL1GL1RyData.Dispose();
249 data.shL1BL1RzData.Dispose();
250 }
251
252 if (data.shL2Data_0.IsCreated)
253 {
254 data.shL2Data_0.Dispose();
255 data.shL2Data_1.Dispose();
256 data.shL2Data_2.Dispose();
257 data.shL2Data_3.Dispose();
258 }
259
260 if (data.probeOcclusion.IsCreated)
261 {
262 data.probeOcclusion.Dispose();
263 }
264 }
265
266 public void Cleanup(bool cleanScenarioList)
267 {
268 // GPU Data. Will not exist if disk streaming is enabled.
269 if (validityNeighMaskData.IsCreated)
270 {
271 validityNeighMaskData.Dispose();
272 validityNeighMaskData = default;
273
274 foreach (var scenario in scenarios.Values)
275 CleanupPerScenarioData(scenario);
276 }
277
278 // When using disk streaming, we don't want to clear this list as it's the only place where we know which scenarios are available for the cell
279 // This is ok because the scenario data isn't instantiated here.
280 if (cleanScenarioList)
281 scenarios.Clear();
282
283 // Bricks and support data. May not exist with disk streaming.
284 if (bricks.IsCreated)
285 {
286 bricks.Dispose();
287 bricks = default;
288 }
289
290 if (skyOcclusionDataL0L1.IsCreated)
291 {
292 skyOcclusionDataL0L1.Dispose();
293 skyOcclusionDataL0L1 = default;
294 }
295
296 if (skyShadingDirectionIndices.IsCreated)
297 {
298 skyShadingDirectionIndices.Dispose();
299 skyShadingDirectionIndices = default;
300 }
301
302 if (probePositions.IsCreated)
303 {
304 probePositions.Dispose();
305 probePositions = default;
306 }
307
308 if (touchupVolumeInteraction.IsCreated)
309 {
310 touchupVolumeInteraction.Dispose();
311 touchupVolumeInteraction = default;
312 }
313
314 if (validity.IsCreated)
315 {
316 validity.Dispose();
317 validity = default;
318 }
319
320 if (layer.IsCreated)
321 {
322 layer.Dispose();
323 layer = default;
324 }
325
326 if (offsetVectors.IsCreated)
327 {
328 offsetVectors.Dispose();
329 offsetVectors = default;
330 }
331 }
332 }
333
334 internal class CellPoolInfo
335 {
336 public List<Chunk> chunkList = new List<Chunk>();
337 public int shChunkCount;
338
339 public void Clear()
340 {
341 chunkList.Clear();
342 }
343 }
344
345 internal class CellIndexInfo
346 {
347 public int[] flatIndicesInGlobalIndirection = null;
348 public ProbeBrickIndex.CellIndexUpdateInfo updateInfo;
349 public bool indexUpdated;
350 public IndirectionEntryInfo[] indirectionEntryInfo;
351 public int indexChunkCount;
352
353 public void Clear()
354 {
355 flatIndicesInGlobalIndirection = null;
356 updateInfo = default(ProbeBrickIndex.CellIndexUpdateInfo);
357 indexUpdated = false;
358 indirectionEntryInfo = null;
359 }
360 }
361
362 internal class CellBlendingInfo
363 {
364 public List<Chunk> chunkList = new List<Chunk>();
365 public float blendingScore;
366 public float blendingFactor;
367 public bool blending;
368
369 public void MarkUpToDate() => blendingScore = float.MaxValue;
370 public bool IsUpToDate() => blendingScore == float.MaxValue;
371 public void ForceReupload() => blendingFactor = -1.0f;
372 public bool ShouldReupload() => blendingFactor == -1.0f;
373 public void Prioritize() => blendingFactor = -2.0f;
374 public bool ShouldPrioritize() => blendingFactor == -2.0f;
375
376 public void Clear()
377 {
378 chunkList.Clear();
379 blendingScore = 0;
380 blendingFactor = 0;
381 blending = false;
382 }
383 }
384
385 internal class CellStreamingInfo
386 {
387 public CellStreamingRequest request = null;
388 public CellStreamingRequest blendingRequest0 = null;
389 public CellStreamingRequest blendingRequest1 = null;
390 public float streamingScore;
391
392 public bool IsStreaming()
393 {
394 return request != null && request.IsStreaming();
395 }
396
397 public bool IsBlendingStreaming()
398 {
399 return blendingRequest0 != null && blendingRequest0.IsStreaming()
400 || blendingRequest1 != null && blendingRequest1.IsStreaming();
401 }
402
403 public void Clear()
404 {
405 request = null;
406 blendingRequest0 = null;
407 blendingRequest1 = null;
408 streamingScore = 0;
409 }
410 }
411
412 [DebuggerDisplay("Index = {desc.index} Loaded = {loaded}")]
413 internal class Cell : IComparable<Cell>
414 {
415 // Baked data (cell descriptor and baked probe data read from disk).
416 public CellDesc desc;
417 public CellData data;
418 // Runtime info.
419 public CellPoolInfo poolInfo = new CellPoolInfo();
420 public CellIndexInfo indexInfo = new CellIndexInfo();
421 public CellBlendingInfo blendingInfo = new CellBlendingInfo();
422 public CellStreamingInfo streamingInfo = new CellStreamingInfo();
423
424 public int referenceCount = 0;
425 public bool loaded; // "Loaded" means the streaming system decided the cell should be loaded. It does not mean it's ready for GPU consumption (because of blending or disk streaming)
426
427 public CellData.PerScenarioData scenario0;
428 public CellData.PerScenarioData scenario1;
429 public bool hasTwoScenarios;
430
431 public CellInstancedDebugProbes debugProbes;
432
433 public int CompareTo(Cell other)
434 {
435 if (streamingInfo.streamingScore < other.streamingInfo.streamingScore)
436 return -1;
437 else if (streamingInfo.streamingScore > other.streamingInfo.streamingScore)
438 return 1;
439 else
440 return 0;
441 }
442
443 public bool UpdateCellScenarioData(string scenario0, string scenario1)
444 {
445 if(!data.scenarios.TryGetValue(scenario0, out this.scenario0))
446 {
447 return false;
448 }
449
450 hasTwoScenarios = false;
451
452 if (!string.IsNullOrEmpty(scenario1))
453 {
454 if (data.scenarios.TryGetValue(scenario1, out this.scenario1))
455 hasTwoScenarios = true;
456 }
457
458 return true;
459 }
460
461 public void Clear()
462 {
463 desc = null;
464 data = null;
465 poolInfo.Clear();
466 indexInfo.Clear();
467 blendingInfo.Clear();
468 streamingInfo.Clear();
469
470 referenceCount = 0;
471 loaded = false;
472 scenario0 = default;
473 scenario1 = default;
474 hasTwoScenarios = false;
475
476 debugProbes = null;
477 }
478 }
479
480 internal struct Volume : IEquatable<Volume>
481 {
482 internal Vector3 corner;
483 internal Vector3 X; // the vectors are NOT normalized, their length determines the size of the box
484 internal Vector3 Y;
485 internal Vector3 Z;
486
487 internal float maxSubdivisionMultiplier;
488 internal float minSubdivisionMultiplier;
489
490 public Volume(Matrix4x4 trs, float maxSubdivision, float minSubdivision)
491 {
492 X = trs.GetColumn(0);
493 Y = trs.GetColumn(1);
494 Z = trs.GetColumn(2);
495 corner = (Vector3)trs.GetColumn(3) - X * 0.5f - Y * 0.5f - Z * 0.5f;
496 this.maxSubdivisionMultiplier = maxSubdivision;
497 this.minSubdivisionMultiplier = minSubdivision;
498 }
499
500 public Volume(Vector3 corner, Vector3 X, Vector3 Y, Vector3 Z, float maxSubdivision = 1, float minSubdivision = 0)
501 {
502 this.corner = corner;
503 this.X = X;
504 this.Y = Y;
505 this.Z = Z;
506 this.maxSubdivisionMultiplier = maxSubdivision;
507 this.minSubdivisionMultiplier = minSubdivision;
508 }
509
510 public Volume(Volume copy)
511 {
512 X = copy.X;
513 Y = copy.Y;
514 Z = copy.Z;
515 corner = copy.corner;
516 maxSubdivisionMultiplier = copy.maxSubdivisionMultiplier;
517 minSubdivisionMultiplier = copy.minSubdivisionMultiplier;
518 }
519
520 public Volume(Bounds bounds)
521 {
522 var size = bounds.size;
523 corner = bounds.center - size * 0.5f;
524 X = new Vector3(size.x, 0, 0);
525 Y = new Vector3(0, size.y, 0);
526 Z = new Vector3(0, 0, size.z);
527
528 maxSubdivisionMultiplier = minSubdivisionMultiplier = 0;
529 }
530
531 public Bounds CalculateAABB()
532 {
533 Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
534 Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
535
536 for (int x = 0; x < 2; x++)
537 {
538 for (int y = 0; y < 2; y++)
539 {
540 for (int z = 0; z < 2; z++)
541 {
542 Vector3 dir = new Vector3(x, y, z);
543
544 Vector3 pt = corner
545 + X * dir.x
546 + Y * dir.y
547 + Z * dir.z;
548
549 min = Vector3.Min(min, pt);
550 max = Vector3.Max(max, pt);
551 }
552 }
553 }
554
555 return new Bounds((min + max) / 2, max - min);
556 }
557
558 public void CalculateCenterAndSize(out Vector3 center, out Vector3 size)
559 {
560 size = new Vector3(X.magnitude, Y.magnitude, Z.magnitude);
561 center = corner + X * 0.5f + Y * 0.5f + Z * 0.5f;
562 }
563
564 public void Transform(Matrix4x4 trs)
565 {
566 corner = trs.MultiplyPoint(corner);
567 X = trs.MultiplyVector(X);
568 Y = trs.MultiplyVector(Y);
569 Z = trs.MultiplyVector(Z);
570 }
571
572 public override string ToString()
573 {
574 return $"Corner: {corner}, X: {X}, Y: {Y}, Z: {Z}, MaxSubdiv: {maxSubdivisionMultiplier}";
575 }
576
577 public bool Equals(Volume other)
578 {
579 return corner == other.corner
580 && X == other.X
581 && Y == other.Y
582 && Z == other.Z
583 && minSubdivisionMultiplier == other.minSubdivisionMultiplier
584 && maxSubdivisionMultiplier == other.maxSubdivisionMultiplier;
585 }
586 }
587
588 internal struct RefVolTransform
589 {
590 public Vector3 posWS;
591 public Quaternion rot;
592 public float scale;
593 }
594
595 /// <summary>
596 /// The resources that are bound to the runtime shaders for sampling Adaptive Probe Volume data.
597 /// </summary>
598 public struct RuntimeResources
599 {
600 /// <summary>
601 /// Index data to fetch the correct location in the Texture3D.
602 /// </summary>
603 public ComputeBuffer index;
604 /// <summary>
605 /// Indices of the various index buffers for each cell.
606 /// </summary>
607 public ComputeBuffer cellIndices;
608 /// <summary>
609 /// Texture containing Spherical Harmonics L0 band data and first coefficient of L1_R.
610 /// </summary>
611 public RenderTexture L0_L1rx;
612 /// <summary>
613 /// Texture containing the second channel of Spherical Harmonics L1 band data and second coefficient of L1_R.
614 /// </summary>
615 public RenderTexture L1_G_ry;
616 /// <summary>
617 /// Texture containing the second channel of Spherical Harmonics L1 band data and third coefficient of L1_R.
618 /// </summary>
619 public RenderTexture L1_B_rz;
620 /// <summary>
621 /// Texture containing the first coefficient of Spherical Harmonics L2 band data and first channel of the fifth.
622 /// </summary>
623 public RenderTexture L2_0;
624 /// <summary>
625 /// Texture containing the second coefficient of Spherical Harmonics L2 band data and second channel of the fifth.
626 /// </summary>
627 public RenderTexture L2_1;
628 /// <summary>
629 /// Texture containing the third coefficient of Spherical Harmonics L2 band data and third channel of the fifth.
630 /// </summary>
631 public RenderTexture L2_2;
632 /// <summary>
633 /// Texture containing the fourth coefficient of Spherical Harmonics L2 band data.
634 /// </summary>
635 public RenderTexture L2_3;
636
637 /// <summary>
638 /// Texture containing 4 light occlusion coefficients for each probe.
639 /// </summary>
640 public RenderTexture ProbeOcclusion;
641
642 /// <summary>
643 /// Texture containing packed validity binary data for the neighbourhood of each probe. Only used when L1. Otherwise this info is stored
644 /// in the alpha channel of L2_3.
645 /// </summary>
646 public RenderTexture Validity;
647
648 /// <summary>
649 /// Texture containing Sky Occlusion SH data (only L0 and L1 band)
650 /// </summary>
651 public RenderTexture SkyOcclusionL0L1;
652
653 /// <summary>
654 /// Texture containing Sky Shading direction indices
655 /// </summary>
656 public RenderTexture SkyShadingDirectionIndices;
657
658 /// <summary>
659 /// Precomputed table of shading directions for sky occlusion shading.
660 /// </summary>
661 public ComputeBuffer SkyPrecomputedDirections;
662 /// <summary>
663 /// Precomputed table of sampling mask for quality leak reduction.
664 /// </summary>
665 public ComputeBuffer QualityLeakReductionData;
666 }
667
668 bool m_IsInitialized = false;
669 bool m_SupportScenarios = false;
670 bool m_SupportScenarioBlending = false;
671 bool m_ForceNoDiskStreaming = false;
672 bool m_SupportDiskStreaming = false;
673 bool m_SupportGPUStreaming = false;
674 bool m_UseStreamingAssets = true;
675 float m_MinBrickSize;
676 int m_MaxSubdivision;
677 Vector3 m_ProbeOffset;
678 ProbeBrickPool m_Pool;
679 ProbeBrickIndex m_Index;
680 ProbeGlobalIndirection m_CellIndices;
681 ProbeBrickBlendingPool m_BlendingPool;
682 List<Chunk> m_TmpSrcChunks = new List<Chunk>();
683 float[] m_PositionOffsets = new float[ProbeBrickPool.kBrickProbeCountPerDim];
684 Bounds m_CurrGlobalBounds = new Bounds();
685
686 internal Bounds globalBounds { get { return m_CurrGlobalBounds; } set { m_CurrGlobalBounds = value; } }
687
688 internal Dictionary<int, Cell> cells = new Dictionary<int, Cell>();
689 ObjectPool<Cell> m_CellPool = new ObjectPool<Cell>(x => x.Clear(), null, false);
690
691 ProbeBrickPool.DataLocation m_TemporaryDataLocation;
692 int m_TemporaryDataLocationMemCost;
693
694#pragma warning disable 618
695 [Obsolete("This field is only kept for migration purpose.")]
696 internal ProbeVolumeSceneData sceneData; // Kept for migration
697#pragma warning restore 618
698
699 // We need to keep track the area, in cells, that is currently loaded. The index buffer will cover even unloaded areas, but we want to avoid sampling outside those areas.
700 Vector3Int minLoadedCellPos = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
701 Vector3Int maxLoadedCellPos = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
702
703 /// <summary>
704 /// The input to the retrieveExtraDataAction action.
705 /// </summary>
706 public struct ExtraDataActionInput
707 {
708 // Empty, but defined to make this future proof without having to change public API
709 }
710
711 /// <summary>
712 /// An action that is used by the SRP to retrieve extra data that was baked together with the bake
713 /// </summary>
714 public Action<ExtraDataActionInput> retrieveExtraDataAction;
715
716 /// <summary>
717 /// An action that is used by the SRP to perform checks every frame during baking.
718 /// </summary>
719 public Action checksDuringBakeAction = null;
720
721 // Information of the probe volume scenes that is being loaded (if one is pending)
722 Dictionary<string, (ProbeVolumeBakingSet, List<int>)> m_PendingScenesToBeLoaded = new Dictionary<string, (ProbeVolumeBakingSet, List<int>)>();
723
724 // Information on probes we need to remove.
725 Dictionary<string, List<int>> m_PendingScenesToBeUnloaded = new Dictionary<string, List<int>>();
726 // Information of the probe volume scenes that is being loaded (if one is pending)
727 List<string> m_ActiveScenes = new List<string>();
728
729 ProbeVolumeBakingSet m_CurrentBakingSet = null;
730
731 bool m_NeedLoadAsset = false;
732 bool m_ProbeReferenceVolumeInit = false;
733 bool m_EnabledBySRP = false;
734 bool m_VertexSampling = false;
735
736 /// <summary>Is Probe Volume initialized.</summary>
737 public bool isInitialized => m_IsInitialized;
738 internal bool enabledBySRP => m_EnabledBySRP;
739 internal bool vertexSampling => m_VertexSampling;
740
741 internal bool hasUnloadedCells => m_ToBeLoadedCells.size != 0;
742
743 internal bool supportLightingScenarios => m_SupportScenarios;
744 internal bool supportScenarioBlending => m_SupportScenarioBlending;
745 internal bool gpuStreamingEnabled => m_SupportGPUStreaming;
746 internal bool diskStreamingEnabled => m_SupportDiskStreaming && !m_ForceNoDiskStreaming;
747
748 /// <summary>
749 /// Whether APV stores occlusion for mixed lights.
750 /// </summary>
751 public bool probeOcclusion
752 {
753 get => m_CurrentBakingSet ? m_CurrentBakingSet.bakedProbeOcclusion : false;
754 }
755
756 /// <summary>
757 /// Whether APV handles sky dynamically (with baked sky occlusion) or fully statically.
758 /// </summary>
759 public bool skyOcclusion
760 {
761 get => m_CurrentBakingSet ? m_CurrentBakingSet.bakedSkyOcclusion : false;
762 }
763
764 /// <summary>
765 /// Bake sky shading direction.
766 /// </summary>
767 public bool skyOcclusionShadingDirection
768 {
769 get => m_CurrentBakingSet ? m_CurrentBakingSet.bakedSkyShadingDirection : false;
770 }
771
772 bool useRenderingLayers => m_CurrentBakingSet.bakedMaskCount != 1;
773
774
775 bool m_NeedsIndexRebuild = false;
776 bool m_HasChangedIndex = false;
777
778 int m_CBShaderID = Shader.PropertyToID("ShaderVariablesProbeVolumes");
779
780 ProbeVolumeTextureMemoryBudget m_MemoryBudget;
781 ProbeVolumeBlendingTextureMemoryBudget m_BlendingMemoryBudget;
782 ProbeVolumeSHBands m_SHBands;
783
784 /// <summary>
785 /// The <see cref="ProbeVolumeSHBands"/>
786 /// </summary>
787 public ProbeVolumeSHBands shBands => m_SHBands;
788
789 internal bool clearAssetsOnVolumeClear = false;
790
791 /// <summary>The active baking set.</summary>
792 public ProbeVolumeBakingSet currentBakingSet => m_CurrentBakingSet;
793
794 /// <summary>The active lighting scenario.</summary>
795 public string lightingScenario
796 {
797 get => m_CurrentBakingSet ? m_CurrentBakingSet.lightingScenario : null;
798 set
799 {
800 SetActiveScenario(value);
801 }
802 }
803
804 /// <summary>The lighting scenario APV is blending toward.</summary>
805 public string otherScenario
806 {
807 get => m_CurrentBakingSet ? m_CurrentBakingSet.otherScenario : null;
808 }
809
810 /// <summary>The blending factor currently used to blend probe data. A value of 0 means blending is not active.</summary>
811 public float scenarioBlendingFactor
812 {
813 get => m_CurrentBakingSet ? m_CurrentBakingSet.scenarioBlendingFactor : 0.0f;
814 set
815 {
816 if (m_CurrentBakingSet != null)
817 m_CurrentBakingSet.BlendLightingScenario(m_CurrentBakingSet.otherScenario, value);
818 }
819 }
820 static internal string GetSceneGUID(Scene scene) => scene.GetGUID();
821
822 internal void SetActiveScenario(string scenario, bool verbose = true)
823 {
824 if (m_CurrentBakingSet != null)
825 m_CurrentBakingSet.SetActiveScenario(scenario, verbose);
826 }
827
828 /// <summary>Allows smooth transitions between two lighting scenarios. This only affects the runtime data used for lighting.</summary>
829 /// <param name="otherScenario">The name of the scenario to load.</param>
830 /// <param name="blendingFactor">The factor used to interpolate between the active scenario and otherScenario. Accepted values range from 0 to 1 and will progressively blend from the active scenario to otherScenario.</param>
831 public void BlendLightingScenario(string otherScenario, float blendingFactor)
832 {
833 if (m_CurrentBakingSet != null)
834 m_CurrentBakingSet.BlendLightingScenario(otherScenario, blendingFactor);
835 }
836
837 internal static string defaultLightingScenario = "Default";
838
839 /// <summary>
840 /// Get the memory budget for the Probe Volume system.
841 /// </summary>
842 public ProbeVolumeTextureMemoryBudget memoryBudget => m_MemoryBudget;
843
844 static ProbeReferenceVolume _instance = new ProbeReferenceVolume();
845
846 internal List<ProbeVolumePerSceneData> perSceneDataList { get; private set; } = new List<ProbeVolumePerSceneData>();
847
848 internal void RegisterPerSceneData(ProbeVolumePerSceneData data)
849 {
850 if (!perSceneDataList.Contains(data))
851 {
852 perSceneDataList.Add(data);
853
854 // Registration can happen before APV (or even the current pipeline) is initialized, so in this case we need to delay the init.
855 if (m_IsInitialized)
856 data.Initialize();
857 }
858 }
859
860 /// <summary>
861 /// Loads the baking set the given scene is part of if it exists.
862 /// </summary>
863 /// <param name="scene">The scene for which to load the baking set.</param>
864 public void SetActiveScene(Scene scene)
865 {
866 if (TryGetPerSceneData(GetSceneGUID(scene), out var perSceneData))
867 SetActiveBakingSet(perSceneData.serializedBakingSet);
868 }
869
870 /// <summary>
871 /// Set the currently active baking set.
872 /// Can be used when loading additively two scenes belonging to different baking sets to control which one is active.
873 /// </summary>
874 /// <param name="bakingSet">The baking set to load.</param>
875 public void SetActiveBakingSet(ProbeVolumeBakingSet bakingSet)
876 {
877 if (m_CurrentBakingSet == bakingSet)
878 return;
879
880 foreach (var data in perSceneDataList)
881 data.QueueSceneRemoval();
882
883 UnloadBakingSet();
884 SetBakingSetAsCurrent(bakingSet);
885
886 if (m_CurrentBakingSet != null)
887 {
888 foreach (var data in perSceneDataList)
889 data.QueueSceneLoading();
890 }
891 }
892
893 void SetBakingSetAsCurrent(ProbeVolumeBakingSet bakingSet)
894 {
895 m_CurrentBakingSet = bakingSet;
896
897 // Can happen when you have only one scene loaded and you remove it from any baking set.
898 if (m_CurrentBakingSet != null)
899 {
900 // Delay first time init to after baking set is loaded to ensure we allocate what's needed
901 InitProbeReferenceVolume();
902
903 m_CurrentBakingSet.Initialize(m_UseStreamingAssets);
904 m_CurrGlobalBounds = m_CurrentBakingSet.globalBounds;
905 SetSubdivisionDimensions(bakingSet.minBrickSize, bakingSet.maxSubdivision, bakingSet.bakedProbeOffset);
906
907 m_NeedsIndexRebuild = true;
908 }
909 }
910
911 internal void RegisterBakingSet(ProbeVolumePerSceneData data)
912 {
913 if (m_CurrentBakingSet == null)
914 {
915 SetBakingSetAsCurrent(data.serializedBakingSet);
916 }
917 }
918
919 internal void UnloadBakingSet()
920 {
921 // Need to make sure everything is unloaded before killing the baking set ref (we need it to unload cell CPU data).
922 PerformPendingOperations();
923
924 if (m_CurrentBakingSet != null)
925 m_CurrentBakingSet.Cleanup();
926 m_CurrentBakingSet = null;
927 m_CurrGlobalBounds = new Bounds();
928
929 // Restart pool from zero to avoid unnecessary memory consumption when going from a big to a small scene.
930 if (m_ScratchBufferPool != null)
931 {
932 m_ScratchBufferPool.Cleanup();
933 m_ScratchBufferPool = null;
934 }
935 }
936
937 internal void UnregisterPerSceneData(ProbeVolumePerSceneData data)
938 {
939 perSceneDataList.Remove(data);
940 if (perSceneDataList.Count == 0)
941 UnloadBakingSet();
942 }
943
944 internal bool TryGetPerSceneData(string sceneGUID, out ProbeVolumePerSceneData perSceneData)
945 {
946 foreach (var data in perSceneDataList)
947 {
948 if (GetSceneGUID(data.gameObject.scene) == sceneGUID)
949 {
950 perSceneData = data;
951 return true;
952 }
953 }
954
955 perSceneData = null;
956 return false;
957 }
958
959 internal float indexFragmentationRate { get => m_ProbeReferenceVolumeInit ? m_Index.fragmentationRate : 0; }
960
961 /// <summary>
962 /// Get the instance of the probe reference volume (singleton).
963 /// </summary>
964 public static ProbeReferenceVolume instance => _instance;
965
966 /// <summary>
967 /// Initialize the Probe Volume system
968 /// </summary>
969 /// <param name="parameters">Initialization parameters.</param>
970 public void Initialize(in ProbeVolumeSystemParameters parameters)
971 {
972 if (m_IsInitialized)
973 {
974 Debug.LogError("Probe Volume System has already been initialized.");
975 return;
976 }
977
978 var probeVolumeSettings = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeGlobalSettings>();
979
980 m_MemoryBudget = parameters.memoryBudget;
981 m_BlendingMemoryBudget = parameters.blendingMemoryBudget;
982 m_SupportScenarios = parameters.supportScenarios;
983 m_SupportScenarioBlending = parameters.supportScenarios && parameters.supportScenarioBlending && SystemInfo.supportsComputeShaders && m_BlendingMemoryBudget != 0;
984 m_SHBands = parameters.shBands;
985 m_UseStreamingAssets = !probeVolumeSettings.probeVolumeDisableStreamingAssets;
986#if UNITY_EDITOR
987 // In editor we can always use Streaming Assets. This optimizes memory usage for editing.
988 m_UseStreamingAssets = true;
989#endif
990 m_SupportGPUStreaming = parameters.supportGPUStreaming;
991 // GPU Streaming is required for Disk Streaming
992 var streamingUploadCS = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeRuntimeResources>()?.probeVolumeUploadDataCS;
993 var streamingUploadL2CS = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeRuntimeResources>()?.probeVolumeUploadDataL2CS;
994 m_SupportDiskStreaming = parameters.supportDiskStreaming && SystemInfo.supportsComputeShaders && m_SupportGPUStreaming && m_UseStreamingAssets && streamingUploadCS != null && streamingUploadL2CS != null;
995 // For now this condition is redundant with m_SupportDiskStreaming but we plan to support disk streaming without compute in the future.
996 // So we need to split the conditions to plan for that.
997 m_DiskStreamingUseCompute = SystemInfo.supportsComputeShaders && streamingUploadCS != null && streamingUploadL2CS != null;
998 InitializeDebug();
999 ProbeVolumeConstantRuntimeResources.Initialize();
1000 ProbeBrickPool.Initialize();
1001 ProbeBrickBlendingPool.Initialize();
1002 InitStreaming();
1003
1004 m_IsInitialized = true;
1005 m_NeedsIndexRebuild = true;
1006#pragma warning disable 618
1007 sceneData = parameters.sceneData;
1008#pragma warning restore 618
1009
1010#if UNITY_EDITOR
1011 UnityEditor.SceneManagement.EditorSceneManager.sceneSaving += ProbeVolumeBakingSet.OnSceneSaving;
1012 ProbeVolumeBakingSet.SyncBakingSets();
1013#endif
1014 m_EnabledBySRP = true;
1015
1016 foreach (var data in perSceneDataList)
1017 data.Initialize();
1018 }
1019
1020 /// <summary>
1021 /// Communicate to the Probe Volume system whether the SRP enables Probe Volume.
1022 /// It is important to keep in mind that this is not used by the system for anything else but book-keeping,
1023 /// the SRP is still responsible to disable anything Probe volume related on SRP side.
1024 /// </summary>
1025 /// <param name="srpEnablesPV">The value of the new enabled</param>
1026 public void SetEnableStateFromSRP(bool srpEnablesPV)
1027 {
1028 m_EnabledBySRP = srpEnablesPV;
1029 }
1030
1031 /// <summary>
1032 /// Communicate to the Probe Volume system whether the SRP uses per vertex sampling
1033 /// </summary>
1034 /// <param name="value">True for vertex sampling, false for pixel sampling</param>
1035 public void SetVertexSamplingEnabled(bool value)
1036 {
1037 m_VertexSampling = value;
1038 }
1039
1040 // This is used for steps such as dilation that require the maximum order allowed to be loaded at all times. Should really never be used as a general purpose function.
1041 internal void ForceSHBand(ProbeVolumeSHBands shBands)
1042 {
1043 m_SHBands = shBands;
1044
1045 DeinitProbeReferenceVolume();
1046
1047 foreach (var data in perSceneDataList)
1048 data.Initialize();
1049
1050 PerformPendingOperations();
1051 }
1052
1053 internal void ForceNoDiskStreaming(bool state)
1054 {
1055 m_ForceNoDiskStreaming = state;
1056 }
1057
1058 /// <summary>
1059 /// Cleanup the Probe Volume system.
1060 /// </summary>
1061 public void Cleanup()
1062 {
1063 CoreUtils.SafeRelease(m_EmptyIndexBuffer);
1064 m_EmptyIndexBuffer = null;
1065 ProbeVolumeConstantRuntimeResources.Cleanup();
1066
1067#if UNITY_EDITOR
1068 UnityEditor.SceneManagement.EditorSceneManager.sceneSaving -= ProbeVolumeBakingSet.OnSceneSaving;
1069#endif
1070
1071 if (!m_IsInitialized)
1072 {
1073 Debug.LogError("Adaptive Probe Volumes have not been initialized before calling Cleanup.");
1074 return;
1075 }
1076
1077 CleanupLoadedData();
1078 CleanupDebug();
1079 CleanupStreaming();
1080 DeinitProbeReferenceVolume();
1081 m_IsInitialized = false;
1082 }
1083
1084 /// <summary>
1085 /// Get approximate video memory impact, in bytes, of the system.
1086 /// </summary>
1087 /// <returns>An approximation of the video memory impact, in bytes, of the system</returns>
1088 public int GetVideoMemoryCost()
1089 {
1090 if (!m_ProbeReferenceVolumeInit)
1091 return 0;
1092
1093 return m_Pool.estimatedVMemCost + m_Index.estimatedVMemCost + m_CellIndices.estimatedVMemCost + m_BlendingPool.estimatedVMemCost + m_TemporaryDataLocationMemCost;
1094 }
1095
1096 void RemoveCell(int cellIndex)
1097 {
1098 if (cells.TryGetValue(cellIndex, out var cellInfo))
1099 {
1100 cellInfo.referenceCount--;
1101 if (cellInfo.referenceCount <= 0)
1102 {
1103 cells.Remove(cellIndex);
1104
1105 if (cellInfo.loaded)
1106 {
1107 m_LoadedCells.Remove(cellInfo);
1108 UnloadCell(cellInfo);
1109 }
1110 else
1111 {
1112 m_ToBeLoadedCells.Remove(cellInfo);
1113 }
1114
1115 m_CurrentBakingSet.ReleaseCell(cellIndex);
1116 m_CellPool.Release(cellInfo);
1117 }
1118 }
1119 }
1120
1121 // This one is internal for baking purpose only.
1122 // Calling this from "outside" will not properly update Loaded/ToBeLoadedCells arrays and thus will break the state of streaming.
1123 internal void UnloadCell(Cell cell)
1124 {
1125 // Streaming might have never loaded the cell in the first place
1126 if (cell.loaded)
1127 {
1128 if (cell.blendingInfo.blending)
1129 {
1130 m_LoadedBlendingCells.Remove(cell);
1131 UnloadBlendingCell(cell);
1132 }
1133 else
1134 m_ToBeLoadedBlendingCells.Remove(cell);
1135
1136 if (cell.indexInfo.flatIndicesInGlobalIndirection != null)
1137 m_CellIndices.MarkEntriesAsUnloaded(cell.indexInfo.flatIndicesInGlobalIndirection);
1138
1139 if (diskStreamingEnabled)
1140 {
1141 if (cell.streamingInfo.IsStreaming())
1142 {
1143 CancelStreamingRequest(cell);
1144 }
1145 else
1146 {
1147 ReleaseBricks(cell);
1148 cell.data.Cleanup(!diskStreamingEnabled); // Release CPU data
1149 }
1150 }
1151 else
1152 ReleaseBricks(cell);
1153
1154 cell.loaded = false;
1155 cell.debugProbes = null;
1156
1157 ClearDebugData();
1158 }
1159 }
1160
1161 internal void UnloadBlendingCell(Cell cell)
1162 {
1163 if (diskStreamingEnabled && cell.streamingInfo.IsBlendingStreaming())
1164 CancelBlendingStreamingRequest(cell);
1165
1166 if (cell.blendingInfo.blending)
1167 {
1168 m_BlendingPool.Deallocate(cell.blendingInfo.chunkList);
1169 cell.blendingInfo.chunkList.Clear();
1170 cell.blendingInfo.blending = false;
1171 }
1172 }
1173
1174 internal void UnloadAllCells()
1175 {
1176 for (int i = 0; i < m_LoadedCells.size; ++i)
1177 UnloadCell(m_LoadedCells[i]);
1178
1179 m_ToBeLoadedCells.AddRange(m_LoadedCells);
1180 m_LoadedCells.Clear();
1181 }
1182
1183 internal void UnloadAllBlendingCells()
1184 {
1185 for (int i = 0; i < m_LoadedBlendingCells.size; ++i)
1186 UnloadBlendingCell(m_LoadedBlendingCells[i]);
1187
1188 m_ToBeLoadedBlendingCells.AddRange(m_LoadedBlendingCells);
1189 m_LoadedBlendingCells.Clear();
1190 }
1191
1192 void AddCell(int cellIndex)
1193 {
1194 // The same cell can exist in more than one scene
1195 // Need to check existence because we don't want to add cells more than once to streaming structures
1196 // TODO: Check perf if relevant?
1197 if (!cells.TryGetValue(cellIndex, out var cell))
1198 {
1199 var cellDesc = m_CurrentBakingSet.GetCellDesc(cellIndex);
1200
1201 // This can happen if a baking set was cleared and not all scene were loaded.
1202 // This results in stray ProbeVolumeAssets for unloaded scenes that contains cell indices not present in the baking set if it was rebaked partially.
1203 if (cellDesc != null)
1204 {
1205 cell = m_CellPool.Get();
1206 cell.desc = cellDesc;
1207 cell.data = m_CurrentBakingSet.GetCellData(cellIndex);
1208 cell.poolInfo.shChunkCount = cell.desc.shChunkCount;
1209 cell.indexInfo.flatIndicesInGlobalIndirection = m_CellIndices.GetFlatIndicesForCell(cellDesc.position);
1210 cell.indexInfo.indexChunkCount = cell.desc.indexChunkCount;
1211 cell.indexInfo.indirectionEntryInfo = cell.desc.indirectionEntryInfo;
1212 cell.indexInfo.updateInfo.entriesInfo = new ProbeBrickIndex.IndirectionEntryUpdateInfo[cellDesc.indirectionEntryInfo.Length];
1213 cell.referenceCount = 1;
1214
1215 cells[cellIndex] = cell;
1216
1217 m_ToBeLoadedCells.Add(cell);
1218 }
1219 }
1220 else
1221 {
1222 cell.referenceCount++;
1223 }
1224 }
1225
1226 // This one is internal for baking purpose only.
1227 // Calling this from "outside" will not properly update Loaded/ToBeLoadedCells arrays and thus will break the state of streaming.
1228 internal bool LoadCell(Cell cell, bool ignoreErrorLog = false)
1229 {
1230 // First try to allocate pool memory. This is what is most likely to fail.
1231 if (ReservePoolChunks(cell.desc.bricksCount, cell.poolInfo.chunkList, ignoreErrorLog))
1232 {
1233 int indirectionBufferEntries = cell.indexInfo.indirectionEntryInfo.Length;
1234
1235 var indexInfo = cell.indexInfo;
1236
1237 for (int entry = 0; entry < indirectionBufferEntries; ++entry)
1238 {
1239 // TODO: remove, this is for migration
1240 if (!cell.indexInfo.indirectionEntryInfo[entry].hasMinMax)
1241 {
1242 if (cell.data.bricks.IsCreated)
1243 ComputeEntryMinMax(ref cell.indexInfo.indirectionEntryInfo[entry], cell.data.bricks);
1244 else
1245 {
1246 int entrySize = CellSize(GetEntrySubdivLevel());
1247 cell.indexInfo.indirectionEntryInfo[entry].minBrickPos = Vector3Int.zero;
1248 cell.indexInfo.indirectionEntryInfo[entry].maxBrickPosPlusOne = new Vector3Int(entrySize + 1, entrySize + 1, entrySize + 1);
1249 cell.indexInfo.indirectionEntryInfo[entry].hasMinMax = true;
1250 }
1251 }
1252
1253 int brickCountAtResForEntry = GetNumberOfBricksAtSubdiv(cell.indexInfo.indirectionEntryInfo[entry]);
1254 indexInfo.updateInfo.entriesInfo[entry].numberOfChunks = m_Index.GetNumberOfChunks(brickCountAtResForEntry);
1255 }
1256
1257 bool canAllocateCell = m_Index.FindSlotsForEntries(ref indexInfo.updateInfo.entriesInfo);
1258 if (canAllocateCell)
1259 {
1260 bool scenarioValid = cell.UpdateCellScenarioData(lightingScenario, otherScenario);
1261
1262 bool successfulReserve = m_Index.ReserveChunks(indexInfo.updateInfo.entriesInfo, ignoreErrorLog);
1263 Debug.Assert(successfulReserve);
1264
1265 for (int entry = 0; entry < indirectionBufferEntries; ++entry)
1266 {
1267 indexInfo.updateInfo.entriesInfo[entry].minValidBrickIndexForCellAtMaxRes = indexInfo.indirectionEntryInfo[entry].minBrickPos;
1268 indexInfo.updateInfo.entriesInfo[entry].maxValidBrickIndexForCellAtMaxResPlusOne = indexInfo.indirectionEntryInfo[entry].maxBrickPosPlusOne;
1269 indexInfo.updateInfo.entriesInfo[entry].entryPositionInBricksAtMaxRes = indexInfo.indirectionEntryInfo[entry].positionInBricks;
1270 indexInfo.updateInfo.entriesInfo[entry].minSubdivInCell = indexInfo.indirectionEntryInfo[entry].minSubdiv;
1271 indexInfo.updateInfo.entriesInfo[entry].hasOnlyBiggerBricks = indexInfo.indirectionEntryInfo[entry].hasOnlyBiggerBricks;
1272 }
1273 cell.loaded = true;
1274
1275 // Copy proper data inside index buffers and pool textures or kick off streaming request.
1276 if (scenarioValid)
1277 AddBricks(cell);
1278
1279 minLoadedCellPos = Vector3Int.Min(minLoadedCellPos, cell.desc.position);
1280 maxLoadedCellPos = Vector3Int.Max(maxLoadedCellPos, cell.desc.position);
1281
1282 ClearDebugData();
1283
1284 return true;
1285 }
1286 else
1287 {
1288 // Index allocation failed, we need to release the pool chunks.
1289 ReleasePoolChunks(cell.poolInfo.chunkList);
1290 // We know we should have the space (test done in TryLoadCell above) so it's because of fragmentation.
1291 StartIndexDefragmentation();
1292 }
1293
1294 return false;
1295 }
1296
1297 return false;
1298 }
1299
1300 // May not load all cells if there is not enough space given the current budget.
1301 internal void LoadAllCells()
1302 {
1303 int loadedCellsCount = m_LoadedCells.size;
1304 for (int i = 0; i < m_ToBeLoadedCells.size; ++i)
1305 {
1306 Cell cell = m_ToBeLoadedCells[i];
1307 if (LoadCell(cell, ignoreErrorLog: true))
1308 m_LoadedCells.Add(cell);
1309 }
1310
1311 for (int i = loadedCellsCount; i < m_LoadedCells.size; ++i)
1312 {
1313 m_ToBeLoadedCells.Remove(m_LoadedCells[i]);
1314 }
1315 }
1316
1317 // This will compute the min/max position of loaded cells as well as the max number of SH chunk for a cell.
1318 void ComputeCellGlobalInfo()
1319 {
1320 minLoadedCellPos = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
1321 maxLoadedCellPos = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
1322
1323 foreach (var cell in cells.Values)
1324 {
1325 if (cell.loaded)
1326 {
1327 minLoadedCellPos = Vector3Int.Min(cell.desc.position, minLoadedCellPos);
1328 maxLoadedCellPos = Vector3Int.Max(cell.desc.position, maxLoadedCellPos);
1329 }
1330 }
1331 }
1332
1333 internal void AddPendingSceneLoading(string sceneGUID, ProbeVolumeBakingSet bakingSet)
1334 {
1335 if (m_PendingScenesToBeLoaded.ContainsKey(sceneGUID))
1336 {
1337 m_PendingScenesToBeLoaded.Remove(sceneGUID);
1338 }
1339
1340 // User might have loaded other scenes with probe volumes but not belonging to the "single scene" baking set.
1341 if (bakingSet == null && m_CurrentBakingSet != null && m_CurrentBakingSet.singleSceneMode)
1342 return;
1343
1344 if (bakingSet.chunkSizeInBricks != ProbeBrickPool.GetChunkSizeInBrickCount())
1345 {
1346 Debug.LogError($"Trying to load Adaptive Probe Volumes data ({bakingSet.name}) baked with an older incompatible version of APV. Please rebake your data.");
1347 return;
1348 }
1349
1350 if (m_CurrentBakingSet != null && bakingSet != m_CurrentBakingSet)
1351 {
1352 // Trying to load data for a scene from a different baking set than currently loaded ones.
1353 // This should not throw an error, but it's not supported
1354 return;
1355 }
1356
1357 // If we don't have any loaded asset yet, we need to verify the other queued assets.
1358 // Only need to check one entry here, they should all have the same baking set by construction.
1359 if (m_PendingScenesToBeLoaded.Count != 0)
1360 {
1361 foreach(var toBeLoadedBakingSet in m_PendingScenesToBeLoaded.Values)
1362 {
1363 if (bakingSet != toBeLoadedBakingSet.Item1)
1364 {
1365 Debug.LogError($"Trying to load Adaptive Probe Volumes data for a scene from a different baking set from other scenes that are being loaded. " +
1366 $"Please make sure all loaded scenes are in the same baking set.");
1367 return;
1368 }
1369
1370 break;
1371 }
1372 }
1373
1374 m_PendingScenesToBeLoaded.Add(sceneGUID, (bakingSet, m_CurrentBakingSet.GetSceneCellIndexList(sceneGUID)));
1375 m_NeedLoadAsset = true;
1376 }
1377
1378 internal void AddPendingSceneRemoval(string sceneGUID)
1379 {
1380 if (m_PendingScenesToBeLoaded.ContainsKey(sceneGUID))
1381 m_PendingScenesToBeLoaded.Remove(sceneGUID);
1382 if (m_ActiveScenes.Contains(sceneGUID))
1383 m_PendingScenesToBeUnloaded.TryAdd(sceneGUID, m_CurrentBakingSet.GetSceneCellIndexList(sceneGUID));
1384 }
1385
1386 internal void RemovePendingScene(string sceneGUID, List<int> cellList)
1387 {
1388 if (m_ActiveScenes.Contains(sceneGUID))
1389 {
1390 m_ActiveScenes.Remove(sceneGUID);
1391 }
1392
1393 // Remove bricks and empty cells
1394 foreach (var cellIndex in cellList)
1395 {
1396 RemoveCell(cellIndex);
1397 }
1398
1399 ClearDebugData();
1400 ComputeCellGlobalInfo();
1401 }
1402
1403 void PerformPendingIndexChangeAndInit()
1404 {
1405 if (m_NeedsIndexRebuild)
1406 {
1407 CleanupLoadedData();
1408 InitializeGlobalIndirection();
1409 m_HasChangedIndex = true;
1410 m_NeedsIndexRebuild = false;
1411 }
1412 else
1413 {
1414 m_HasChangedIndex = false;
1415 }
1416 }
1417
1418 internal void SetSubdivisionDimensions(float minBrickSize, int maxSubdiv, Vector3 offset)
1419 {
1420 m_MinBrickSize = minBrickSize;
1421 SetMaxSubdivision(maxSubdiv);
1422 m_ProbeOffset = offset;
1423 }
1424
1425 bool LoadCells(List<int> cellIndices)
1426 {
1427 if (m_CurrentBakingSet.ResolveCellData(cellIndices))
1428 {
1429 ClearDebugData();
1430
1431 // Add all the cells to the system.
1432 // They'll be streamed in later on.
1433 for (int i = 0; i < cellIndices.Count; ++i)
1434 {
1435 AddCell(cellIndices[i]);
1436 }
1437
1438 return true;
1439 }
1440
1441 return false;
1442 }
1443
1444 void PerformPendingLoading()
1445 {
1446 if ((m_PendingScenesToBeLoaded.Count == 0 && m_ActiveScenes.Count == 0) || !m_NeedLoadAsset || !m_ProbeReferenceVolumeInit)
1447 return;
1448
1449 m_Pool.EnsureTextureValidity();
1450 m_BlendingPool.EnsureTextureValidity();
1451
1452 // Load the ones that are already active but reload if we said we need to load
1453 if (m_HasChangedIndex)
1454 {
1455 foreach (var sceneGUID in m_ActiveScenes)
1456 {
1457 LoadCells(m_CurrentBakingSet.GetSceneCellIndexList(sceneGUID));
1458 }
1459 }
1460
1461 foreach (var loadRequest in m_PendingScenesToBeLoaded)
1462 {
1463 var sceneGUID = loadRequest.Key;
1464 if (LoadCells(loadRequest.Value.Item2) && !m_ActiveScenes.Contains(sceneGUID))
1465 {
1466 m_ActiveScenes.Add(sceneGUID);
1467 }
1468 }
1469
1470 m_PendingScenesToBeLoaded.Clear();
1471
1472 // Mark the loading as done.
1473 m_NeedLoadAsset = false;
1474 }
1475
1476 void PerformPendingDeletion()
1477 {
1478 foreach (var unloadRequest in m_PendingScenesToBeUnloaded)
1479 {
1480 RemovePendingScene(unloadRequest.Key, unloadRequest.Value);
1481 }
1482
1483 m_PendingScenesToBeUnloaded.Clear();
1484 }
1485
1486 internal void ComputeEntryMinMax(ref IndirectionEntryInfo entryInfo, ReadOnlySpan<Brick> bricks)
1487 {
1488 int entrySize = CellSize(GetEntrySubdivLevel());
1489 Vector3Int entry_min = entryInfo.positionInBricks;
1490 Vector3Int entry_max = entryInfo.positionInBricks + new Vector3Int(entrySize, entrySize, entrySize);
1491
1492 if (entryInfo.hasOnlyBiggerBricks)
1493 {
1494 entryInfo.minBrickPos = entry_min;
1495 entryInfo.maxBrickPosPlusOne = entry_max;
1496 }
1497 else
1498 {
1499 entryInfo.minBrickPos = entryInfo.maxBrickPosPlusOne = Vector3Int.zero;
1500
1501 bool initialized = false;
1502 for (int i = 0; i < bricks.Length; i++)
1503 {
1504 int brickSize = ProbeReferenceVolume.CellSize(bricks[i].subdivisionLevel);
1505 var brickMin = bricks[i].position;
1506 var brickMax = bricks[i].position + new Vector3Int(brickSize, brickSize, brickSize);
1507 if (!ProbeBrickIndex.BrickOverlapEntry(brickMin, brickMax, entry_min, entry_max))
1508 continue;
1509
1510 // Bricks can be bigger than entries !
1511 brickMin = Vector3Int.Max(brickMin, entry_min);
1512 brickMax = Vector3Int.Min(brickMax, entry_max);
1513
1514 if (initialized)
1515 {
1516 entryInfo.minBrickPos = Vector3Int.Min(brickMin, entryInfo.minBrickPos);
1517 entryInfo.maxBrickPosPlusOne = Vector3Int.Max(brickMax, entryInfo.maxBrickPosPlusOne);
1518 }
1519 else
1520 {
1521 entryInfo.minBrickPos = brickMin;
1522 entryInfo.maxBrickPosPlusOne = brickMax;
1523 initialized = true;
1524 }
1525 }
1526 }
1527
1528 entryInfo.minBrickPos = entryInfo.minBrickPos - entry_min;
1529 entryInfo.maxBrickPosPlusOne = Vector3Int.one + entryInfo.maxBrickPosPlusOne - entry_min;
1530 entryInfo.hasMinMax = true;
1531 }
1532
1533 static internal int GetNumberOfBricksAtSubdiv(IndirectionEntryInfo entryInfo)
1534 {
1535 // This is a special case that can be handled manually easily.
1536 if (entryInfo.hasOnlyBiggerBricks)
1537 return 1;
1538
1539 Vector3Int sizeOfValidIndicesAtMaxRes = entryInfo.maxBrickPosPlusOne - entryInfo.minBrickPos;
1540 Vector3Int bricksForEntry = sizeOfValidIndicesAtMaxRes / CellSize(entryInfo.minSubdiv);
1541 return bricksForEntry.x * bricksForEntry.y * bricksForEntry.z;
1542 }
1543
1544 /// <summary>
1545 /// Perform all the operations that are relative to changing the content or characteristics of the probe reference volume.
1546 /// </summary>
1547 public void PerformPendingOperations()
1548 {
1549#if UNITY_EDITOR
1550 checksDuringBakeAction?.Invoke();
1551#endif
1552 PerformPendingDeletion();
1553 PerformPendingIndexChangeAndInit();
1554 PerformPendingLoading();
1555 }
1556
1557 internal void InitializeGlobalIndirection()
1558 {
1559 // Current baking set can be null at init and we still need the buffers to valid.
1560 var minCellPosition = m_CurrentBakingSet ? m_CurrentBakingSet.minCellPosition : Vector3Int.zero;
1561 var maxCellPosition = m_CurrentBakingSet ? m_CurrentBakingSet.maxCellPosition : Vector3Int.zero;
1562 if (m_CellIndices != null)
1563 m_CellIndices.Cleanup();
1564 m_CellIndices = new ProbeGlobalIndirection(minCellPosition, maxCellPosition, Mathf.Max(1, (int)Mathf.Pow(3, m_MaxSubdivision - 1)));
1565 if (m_SupportGPUStreaming)
1566 {
1567 if (m_DefragCellIndices != null)
1568 m_DefragCellIndices.Cleanup();
1569 m_DefragCellIndices = new ProbeGlobalIndirection(minCellPosition, maxCellPosition, Mathf.Max(1, (int)Mathf.Pow(3, m_MaxSubdivision - 1)));
1570 }
1571 }
1572
1573 /// <summary>
1574 /// Initialize the reference volume.
1575 /// </summary>
1576 void InitProbeReferenceVolume()
1577 {
1578 // If a set without sky occlusion was loaded, and a set with sky occlusion is now loaded,
1579 // the pools will not have allocated all necessary buffers
1580 // To support that case, we can force reinit here because we know no scenes are loaded (as we are changing baking set)
1581 if (m_ProbeReferenceVolumeInit && !m_Pool.EnsureTextureValidity(useRenderingLayers, skyOcclusion, skyOcclusionShadingDirection, probeOcclusion))
1582 {
1583 m_TemporaryDataLocation.Cleanup();
1584 m_TemporaryDataLocation = ProbeBrickPool.CreateDataLocation(ProbeBrickPool.GetChunkSizeInProbeCount(), compressed: false, m_SHBands, "APV_Intermediate",
1585 false, true, useRenderingLayers, skyOcclusion, skyOcclusionShadingDirection, probeOcclusion, out m_TemporaryDataLocationMemCost);
1586 }
1587
1588 if (!m_ProbeReferenceVolumeInit)
1589 {
1590 Profiler.BeginSample("Initialize Reference Volume");
1591 m_Pool = new ProbeBrickPool(m_MemoryBudget, m_SHBands, allocateValidityData: true, useRenderingLayers, skyOcclusion, skyOcclusionShadingDirection, probeOcclusion);
1592 m_BlendingPool = new ProbeBrickBlendingPool(m_BlendingMemoryBudget, m_SHBands, probeOcclusion);
1593
1594 m_Index = new ProbeBrickIndex(m_MemoryBudget);
1595
1596 if (m_SupportGPUStreaming)
1597 {
1598 m_DefragIndex = new ProbeBrickIndex(m_MemoryBudget);
1599 }
1600
1601 InitializeGlobalIndirection();
1602
1603 m_TemporaryDataLocation = ProbeBrickPool.CreateDataLocation(ProbeBrickPool.GetChunkSizeInProbeCount(), compressed: false, m_SHBands, "APV_Intermediate",
1604 false, true, useRenderingLayers, skyOcclusion, skyOcclusionShadingDirection, probeOcclusion, out m_TemporaryDataLocationMemCost);
1605
1606 // initialize offsets
1607 m_PositionOffsets[0] = 0.0f;
1608 float probeDelta = 1.0f / ProbeBrickPool.kBrickCellCount;
1609 for (int i = 1; i < ProbeBrickPool.kBrickProbeCountPerDim - 1; i++)
1610 m_PositionOffsets[i] = i * probeDelta;
1611 m_PositionOffsets[m_PositionOffsets.Length - 1] = 1.0f;
1612
1613 Profiler.EndSample();
1614
1615 m_ProbeReferenceVolumeInit = true;
1616
1617 ClearDebugData();
1618
1619 m_NeedLoadAsset = true;
1620 }
1621
1622 // Refresh debug menu
1623 if (DebugManager.instance.GetPanel(k_DebugPanelName, false) != null)
1624 {
1625 instance.UnregisterDebug(false);
1626 instance.RegisterDebug();
1627 }
1628 }
1629
1630 ProbeReferenceVolume()
1631 {
1632 m_MinBrickSize = 1.0f;
1633 }
1634
1635#if UNITY_EDITOR
1636 internal bool EnsureCurrentBakingSet(ProbeVolumeBakingSet bakingSet)
1637 {
1638 //Ensure that all currently loaded scenes belong to the same set.
1639 foreach (var data in perSceneDataList)
1640 {
1641 var set = ProbeVolumeBakingSet.GetBakingSetForScene(data.gameObject.scene);
1642 if (set != bakingSet)
1643 return false;
1644 }
1645
1646 SetBakingSetAsCurrent(bakingSet);
1647 return true;
1648 }
1649#endif
1650
1651 /// <summary>
1652 /// Get the resources that are bound to the runtime shaders for sampling Adaptive Probe Volume data.
1653 /// </summary>
1654 /// <returns>The resources to bind to runtime shaders.</returns>
1655 public RuntimeResources GetRuntimeResources()
1656 {
1657 if (!m_ProbeReferenceVolumeInit)
1658 return default(RuntimeResources);
1659
1660 RuntimeResources rr = new RuntimeResources();
1661 m_Index.GetRuntimeResources(ref rr);
1662 m_CellIndices.GetRuntimeResources(ref rr);
1663 m_Pool.GetRuntimeResources(ref rr);
1664 ProbeVolumeConstantRuntimeResources.GetRuntimeResources(ref rr);
1665 return rr;
1666 }
1667
1668 internal void SetMaxSubdivision(int maxSubdivision)
1669 {
1670 int newValue = Math.Min(maxSubdivision, ProbeBrickIndex.kMaxSubdivisionLevels);
1671 if (newValue != m_MaxSubdivision)
1672 {
1673 m_MaxSubdivision = System.Math.Min(maxSubdivision, ProbeBrickIndex.kMaxSubdivisionLevels);
1674 if (m_CellIndices != null)
1675 {
1676 m_CellIndices.Cleanup();
1677 }
1678 if (m_SupportGPUStreaming && m_DefragCellIndices != null)
1679 {
1680 m_DefragCellIndices.Cleanup();
1681 }
1682 InitializeGlobalIndirection();
1683 }
1684 }
1685
1686 internal static int CellSize(int subdivisionLevel) => (int)Mathf.Pow(ProbeBrickPool.kBrickCellCount, subdivisionLevel);
1687 internal float BrickSize(int subdivisionLevel) => m_MinBrickSize * CellSize(subdivisionLevel);
1688 internal float MinBrickSize() => m_MinBrickSize;
1689 internal float MaxBrickSize() => BrickSize(m_MaxSubdivision - 1);
1690 internal Vector3 ProbeOffset() => m_ProbeOffset;
1691 internal int GetMaxSubdivision() => m_MaxSubdivision;
1692 internal int GetMaxSubdivision(float multiplier) => Mathf.CeilToInt(m_MaxSubdivision * multiplier);
1693 internal float GetDistanceBetweenProbes(int subdivisionLevel) => BrickSize(subdivisionLevel) / 3.0f;
1694 internal float MinDistanceBetweenProbes() => GetDistanceBetweenProbes(0);
1695
1696 // IMPORTANT! IF THIS VALUE CHANGES DATA NEEDS TO BE REBAKED.
1697 internal int GetGlobalIndirectionEntryMaxSubdiv() => ProbeGlobalIndirection.kEntryMaxSubdivLevel;
1698
1699 internal int GetEntrySubdivLevel() => Mathf.Min(ProbeGlobalIndirection.kEntryMaxSubdivLevel, m_MaxSubdivision - 1);
1700 internal float GetEntrySize() => BrickSize(GetEntrySubdivLevel());
1701 /// <summary>
1702 /// Returns whether any brick data has been loaded.
1703 /// </summary>
1704 /// <returns>True if brick data is present, otherwise false.</returns>
1705 public bool DataHasBeenLoaded() => m_LoadedCells.size != 0;
1706
1707 internal void Clear()
1708 {
1709 if (m_ProbeReferenceVolumeInit)
1710 {
1711 try
1712 {
1713 // Need to do that first because some assets may be in the process of being removed.
1714 PerformPendingOperations();
1715 }
1716 finally
1717 {
1718 UnloadAllCells();
1719 m_ToBeLoadedCells.Clear();
1720 m_Pool.Clear();
1721 m_BlendingPool.Clear();
1722 m_Index.Clear();
1723 cells.Clear();
1724
1725 Debug.Assert(m_LoadedCells.size == 0);
1726 }
1727 }
1728
1729 if (clearAssetsOnVolumeClear)
1730 {
1731 m_PendingScenesToBeLoaded.Clear();
1732 m_ActiveScenes.Clear();
1733 }
1734 }
1735
1736 // Currently only used for 1 chunk at a time but kept in case we need more in the future.
1737 List<Chunk> GetSourceLocations(int count, int chunkSize, ProbeBrickPool.DataLocation dataLoc)
1738 {
1739 var c = new Chunk();
1740 m_TmpSrcChunks.Clear();
1741 m_TmpSrcChunks.Add(c);
1742
1743 // currently this code assumes that the texture width is a multiple of the allocation chunk size
1744 for (int j = 1; j < count; j++)
1745 {
1746 c.x += chunkSize * ProbeBrickPool.kBrickProbeCountPerDim;
1747 if (c.x >= dataLoc.width)
1748 {
1749 c.x = 0;
1750 c.y += ProbeBrickPool.kBrickProbeCountPerDim;
1751 if (c.y >= dataLoc.height)
1752 {
1753 c.y = 0;
1754 c.z += ProbeBrickPool.kBrickProbeCountPerDim;
1755 }
1756 }
1757 m_TmpSrcChunks.Add(c);
1758 }
1759
1760 return m_TmpSrcChunks;
1761 }
1762
1763 void UpdateDataLocationTexture<T>(Texture output, NativeArray<T> input) where T : struct
1764 {
1765 var outputNativeArray = (output as Texture3D).GetPixelData<T>(0);
1766 Debug.Assert(outputNativeArray.Length >= input.Length);
1767 outputNativeArray.GetSubArray(0, input.Length).CopyFrom(input);
1768 (output as Texture3D).Apply();
1769 }
1770
1771 void UpdateValidityTextureWithoutMask(Texture output, NativeArray<byte> input)
1772 {
1773 // On some platforms, single channel unorm format isn't supported, so validity uses 4 channel unorm format.
1774 // Then we can't directly copy the data, but need to account for the 3 unused channels.
1775 uint numComponents = GraphicsFormatUtility.GetComponentCount(output.graphicsFormat);
1776 if (numComponents == 1)
1777 {
1778 UpdateDataLocationTexture(output, input);
1779 }
1780 else
1781 {
1782 Debug.Assert(output.graphicsFormat == GraphicsFormat.R8G8B8A8_UNorm);
1783 var outputNativeArray = (output as Texture3D).GetPixelData<(byte, byte, byte, byte)>(0);
1784 Debug.Assert(outputNativeArray.Length >= input.Length);
1785 for (int i = 0; i < input.Length; i++)
1786 {
1787 outputNativeArray[i] = (input[i], input[i], input[i], input[i]);
1788 }
1789 (output as Texture3D).Apply();
1790 }
1791 }
1792
1793 void UpdatePool(List<Chunk> chunkList, CellData.PerScenarioData data, NativeArray<byte> validityNeighMaskData,
1794 NativeArray<ushort> skyOcclusionL0L1Data, NativeArray<byte> skyShadingDirectionIndices, int chunkIndex, int poolIndex)
1795 {
1796 var chunkSizeInProbes = ProbeBrickPool.GetChunkSizeInProbeCount();
1797
1798 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL0_L1rx, data.shL0L1RxData.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1799 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL1_G_ry, data.shL1GL1RyData.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1800 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL1_B_rz, data.shL1BL1RzData.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1801
1802 if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL2 && data.shL2Data_0.Length > 0)
1803 {
1804 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL2_0, data.shL2Data_0.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1805 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL2_1, data.shL2Data_1.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1806 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL2_2, data.shL2Data_2.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1807 UpdateDataLocationTexture(m_TemporaryDataLocation.TexL2_3, data.shL2Data_3.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1808 }
1809
1810 if (probeOcclusion && data.probeOcclusion.Length > 0)
1811 {
1812 UpdateDataLocationTexture(m_TemporaryDataLocation.TexProbeOcclusion, data.probeOcclusion.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1813 }
1814
1815 if (poolIndex == -1) // shared data that don't need to be updated per scenario
1816 {
1817 if (validityNeighMaskData.Length > 0)
1818 {
1819 if (m_CurrentBakingSet.bakedMaskCount == 1)
1820 UpdateValidityTextureWithoutMask(m_TemporaryDataLocation.TexValidity, validityNeighMaskData.GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1821 else
1822 UpdateDataLocationTexture(m_TemporaryDataLocation.TexValidity, validityNeighMaskData.Reinterpret<uint>(1).GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1823 }
1824
1825 if (skyOcclusion && skyOcclusionL0L1Data.Length > 0)
1826 UpdateDataLocationTexture(m_TemporaryDataLocation.TexSkyOcclusion, skyOcclusionL0L1Data.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1827
1828 if (skyOcclusionShadingDirection && skyShadingDirectionIndices.Length > 0)
1829 UpdateDataLocationTexture(m_TemporaryDataLocation.TexSkyShadingDirectionIndices, skyShadingDirectionIndices.GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1830 }
1831
1832 // New data format only uploads one chunk at a time (we need predictable chunk size)
1833 var srcChunks = GetSourceLocations(1, ProbeBrickPool.GetChunkSizeInBrickCount(), m_TemporaryDataLocation);
1834
1835 // Update pool textures with incoming SH data and ignore any potential frame latency related issues for now.
1836 if (poolIndex == -1)
1837 m_Pool.Update(m_TemporaryDataLocation, srcChunks, chunkList, chunkIndex, m_SHBands);
1838 else
1839 m_BlendingPool.Update(m_TemporaryDataLocation, srcChunks, chunkList, chunkIndex, m_SHBands, poolIndex);
1840 }
1841
1842 void UpdatePool(CommandBuffer cmd, List<Chunk> chunkList, CellStreamingScratchBuffer dataBuffer, CellStreamingScratchBufferLayout layout, int poolIndex)
1843 {
1844 // Update pool textures with incoming SH data and ignore any potential frame latency related issues for now.
1845 if (poolIndex == -1)
1846 m_Pool.Update(cmd, dataBuffer, layout, chunkList, updateSharedData: true, m_Pool.GetValidityTexture(), m_SHBands, skyOcclusion, m_Pool.GetSkyOcclusionTexture(), skyOcclusionShadingDirection, m_Pool.GetSkyShadingDirectionIndicesTexture(), probeOcclusion);
1847 else
1848 m_BlendingPool.Update(cmd, dataBuffer, layout, chunkList, m_SHBands, poolIndex, m_Pool.GetValidityTexture(), skyOcclusion, m_Pool.GetSkyOcclusionTexture(), skyOcclusionShadingDirection, m_Pool.GetSkyShadingDirectionIndicesTexture(), probeOcclusion);
1849 }
1850
1851 // Updates data shared by all scenarios (validity, sky occlusion, sky direction)
1852 void UpdateSharedData(List<Chunk> chunkList, NativeArray<byte> validityNeighMaskData, NativeArray<ushort> skyOcclusionData, NativeArray<byte> skyShadingDirectionIndices, int chunkIndex)
1853 {
1854 var chunkSizeInProbes = ProbeBrickPool.GetChunkSizeInBrickCount() * ProbeBrickPool.kBrickProbeCountTotal;
1855
1856 if (m_CurrentBakingSet.bakedMaskCount == 1)
1857 UpdateValidityTextureWithoutMask(m_TemporaryDataLocation.TexValidity, validityNeighMaskData.GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1858 else
1859 UpdateDataLocationTexture(m_TemporaryDataLocation.TexValidity, validityNeighMaskData.Reinterpret<uint>(1).GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1860
1861 if (skyOcclusion && skyOcclusionData.Length > 0)
1862 {
1863 UpdateDataLocationTexture(m_TemporaryDataLocation.TexSkyOcclusion, skyOcclusionData.GetSubArray(chunkIndex * chunkSizeInProbes * 4, chunkSizeInProbes * 4));
1864 }
1865
1866 if (skyOcclusion && skyOcclusionShadingDirection && skyShadingDirectionIndices.Length > 0)
1867 {
1868 UpdateDataLocationTexture(m_TemporaryDataLocation.TexSkyShadingDirectionIndices, skyShadingDirectionIndices.GetSubArray(chunkIndex * chunkSizeInProbes, chunkSizeInProbes));
1869 }
1870
1871 var srcChunks = GetSourceLocations(1, ProbeBrickPool.GetChunkSizeInBrickCount(), m_TemporaryDataLocation);
1872
1873 m_Pool.UpdateValidity(m_TemporaryDataLocation, srcChunks, chunkList, chunkIndex);
1874 }
1875
1876 // Runtime API starts here
1877 bool AddBlendingBricks(Cell cell)
1878 {
1879 Debug.Assert(cell.loaded);
1880
1881 using var pm = new ProfilerMarker("AddBlendingBricks").Auto();
1882
1883 Debug.Assert(cell.blendingInfo.chunkList.Count == 0);
1884
1885 // If no blending is needed, bypass the blending pool and directly update uploaded cells
1886 bool bypassBlending = m_CurrentBakingSet.otherScenario == null || !cell.hasTwoScenarios;
1887
1888 // Try to allocate texture space
1889 if (!bypassBlending && !m_BlendingPool.Allocate(cell.poolInfo.shChunkCount, cell.blendingInfo.chunkList))
1890 return false;
1891
1892 if (diskStreamingEnabled)
1893 {
1894 if (bypassBlending)
1895 {
1896 if (cell.blendingInfo.blendingFactor != scenarioBlendingFactor)
1897 PushDiskStreamingRequest(cell, lightingScenario, -1, m_OnStreamingComplete);
1898
1899 // As we bypass blending, we don't load the blending data so we want to avoid trying to blend them later on.
1900 cell.blendingInfo.MarkUpToDate();
1901 }
1902 else
1903 {
1904 PushDiskStreamingRequest(cell, lightingScenario, 0, m_OnBlendingStreamingComplete);
1905 PushDiskStreamingRequest(cell, otherScenario, 1, m_OnBlendingStreamingComplete);
1906 }
1907 }
1908 else
1909 {
1910 // Now that we are sure probe data will be uploaded, we can register the cell in the pool
1911 if (!cell.indexInfo.indexUpdated)
1912 {
1913 // Update the cell index
1914 UpdateCellIndex(cell);
1915 // Upload validity data directly to main pool - constant per scenario, will not need blending, therefore we use the cellInfo chunk list.
1916 var chunkList = cell.poolInfo.chunkList;
1917 for (int chunkIndex = 0; chunkIndex < chunkList.Count; ++chunkIndex)
1918 UpdateSharedData(chunkList, cell.data.validityNeighMaskData, cell.data.skyOcclusionDataL0L1, cell.data.skyShadingDirectionIndices, chunkIndex);
1919 }
1920
1921 if (bypassBlending)
1922 {
1923 if (cell.blendingInfo.blendingFactor != scenarioBlendingFactor)
1924 {
1925 var chunkList = cell.poolInfo.chunkList;
1926 for (int chunkIndex = 0; chunkIndex < chunkList.Count; ++chunkIndex)
1927 {
1928 // No blending so do the same operation as AddBricks would do. But because cell is already loaded,
1929 // no index or chunk data must change, so only probe values need to be updated
1930 UpdatePool(chunkList, cell.scenario0, cell.data.validityNeighMaskData, cell.data.skyOcclusionDataL0L1, cell.data.skyShadingDirectionIndices, chunkIndex, -1);
1931 }
1932 }
1933
1934 // As we bypass blending, we don't load the blending data so we want to avoid trying to blend them later on.
1935 cell.blendingInfo.MarkUpToDate();
1936 }
1937 else
1938 {
1939 var chunkList = cell.blendingInfo.chunkList;
1940 for (int chunkIndex = 0; chunkIndex < chunkList.Count; ++chunkIndex)
1941 {
1942 UpdatePool(chunkList, cell.scenario0, cell.data.validityNeighMaskData, cell.data.skyOcclusionDataL0L1, cell.data.skyShadingDirectionIndices, chunkIndex, 0);
1943 UpdatePool(chunkList, cell.scenario1, cell.data.validityNeighMaskData, cell.data.skyOcclusionDataL0L1, cell.data.skyShadingDirectionIndices, chunkIndex, 1);
1944 }
1945 }
1946 }
1947
1948 cell.blendingInfo.blending = true;
1949
1950 return true;
1951 }
1952
1953 bool ReservePoolChunks(int brickCount, List<Chunk> chunkList, bool ignoreErrorLog)
1954 {
1955 // calculate the number of chunks necessary
1956 int brickChunksCount = ProbeBrickPool.GetChunkCount(brickCount);
1957 chunkList.Clear();
1958
1959 // Try to allocate texture space
1960 return m_Pool.Allocate(brickChunksCount, chunkList, ignoreErrorLog);
1961 }
1962
1963 void ReleasePoolChunks(List<Chunk> chunkList)
1964 {
1965 m_Pool.Deallocate(chunkList);
1966 chunkList.Clear();
1967 }
1968
1969 void UpdatePoolAndIndex(Cell cell, CellStreamingScratchBuffer dataBuffer, CellStreamingScratchBufferLayout layout, int poolIndex, CommandBuffer cmd)
1970 {
1971 if (diskStreamingEnabled)
1972 {
1973 if (m_DiskStreamingUseCompute)
1974 {
1975 Debug.Assert(dataBuffer.buffer != null);
1976 UpdatePool(cmd, cell.poolInfo.chunkList, dataBuffer, layout, poolIndex);
1977 }
1978 else
1979 {
1980 int chunkCount = cell.poolInfo.chunkList.Count;
1981 int offsetAdjustment = -2 * (chunkCount * 4 * sizeof(uint)); // NOTE: account for offsets adding "2 * (chunkCount * 4 * sizeof(uint))" in the calculations from ProbeVolumeScratchBufferPool::GetOrCreateScratchBufferLayout()
1982
1983 CellData.PerScenarioData data = default;
1984 data.shL0L1RxData = dataBuffer.stagingBuffer.GetSubArray(layout._L0L1rxOffset + offsetAdjustment, chunkCount * layout._L0Size).Reinterpret<ushort>(sizeof(byte));
1985 data.shL1GL1RyData = dataBuffer.stagingBuffer.GetSubArray(layout._L1GryOffset + offsetAdjustment, chunkCount * layout._L1Size);
1986 data.shL1BL1RzData = dataBuffer.stagingBuffer.GetSubArray(layout._L1BrzOffset + offsetAdjustment, chunkCount * layout._L1Size);
1987
1988 NativeArray<byte> validityNeighMaskData = dataBuffer.stagingBuffer.GetSubArray(layout._ValidityOffset + offsetAdjustment, chunkCount * layout._ValiditySize);
1989
1990 if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
1991 {
1992 data.shL2Data_0 = dataBuffer.stagingBuffer.GetSubArray(layout._L2_0Offset + offsetAdjustment, chunkCount * layout._L2Size);
1993 data.shL2Data_1 = dataBuffer.stagingBuffer.GetSubArray(layout._L2_1Offset + offsetAdjustment, chunkCount * layout._L2Size);
1994 data.shL2Data_2 = dataBuffer.stagingBuffer.GetSubArray(layout._L2_2Offset + offsetAdjustment, chunkCount * layout._L2Size);
1995 data.shL2Data_3 = dataBuffer.stagingBuffer.GetSubArray(layout._L2_3Offset + offsetAdjustment, chunkCount * layout._L2Size);
1996 }
1997
1998 if (probeOcclusion && layout._ProbeOcclusionSize > 0)
1999 {
2000 data.probeOcclusion = dataBuffer.stagingBuffer.GetSubArray(layout._ProbeOcclusionOffset + offsetAdjustment, chunkCount * layout._ProbeOcclusionSize);
2001 }
2002
2003 NativeArray<ushort> skyOcclusionData = default;
2004 if (skyOcclusion && layout._SkyOcclusionSize > 0)
2005 {
2006 skyOcclusionData = dataBuffer.stagingBuffer.GetSubArray(layout._SkyOcclusionOffset + offsetAdjustment, chunkCount * layout._SkyOcclusionSize).Reinterpret<ushort>(sizeof(byte));
2007 }
2008
2009 NativeArray<byte> skyOcclusionDirectionData = default;
2010 if (skyOcclusion && skyOcclusionShadingDirection && layout._SkyShadingDirectionSize > 0)
2011 {
2012 skyOcclusionDirectionData = dataBuffer.stagingBuffer.GetSubArray(layout._SkyShadingDirectionOffset + offsetAdjustment, chunkCount * layout._SkyShadingDirectionSize);
2013 }
2014
2015 for (int chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex)
2016 {
2017 UpdatePool(cell.poolInfo.chunkList, data, validityNeighMaskData, skyOcclusionData, skyOcclusionDirectionData, chunkIndex, poolIndex);
2018 }
2019 }
2020 }
2021 else
2022 {
2023 // In order not to pre-allocate for the worse case, we update the texture by smaller chunks with a preallocated DataLoc
2024 for (int chunkIndex = 0; chunkIndex < cell.poolInfo.chunkList.Count; ++chunkIndex)
2025 UpdatePool(cell.poolInfo.chunkList, cell.scenario0, cell.data.validityNeighMaskData, cell.data.skyOcclusionDataL0L1, cell.data.skyShadingDirectionIndices, chunkIndex, poolIndex);
2026 }
2027
2028 // Index may already be updated when simply switching scenarios.
2029 if (!cell.indexInfo.indexUpdated)
2030 UpdateCellIndex(cell);
2031 }
2032
2033 bool AddBricks(Cell cell)
2034 {
2035 using var pm = new ProfilerMarker("AddBricks").Auto();
2036
2037 if (supportScenarioBlending) // Register this cell for blending system
2038 m_ToBeLoadedBlendingCells.Add(cell);
2039
2040 // If blending is enabled, we rely on it to upload data already blended to avoid popping
2041 // If enabled but blending factor is 0, upload here in case blending pool is not already allocated
2042 if (!supportScenarioBlending || scenarioBlendingFactor == 0.0f || !cell.hasTwoScenarios)
2043 {
2044 if (diskStreamingEnabled)
2045 {
2046 PushDiskStreamingRequest(cell, m_CurrentBakingSet.lightingScenario, -1, m_OnStreamingComplete);
2047 }
2048 else
2049 {
2050 UpdatePoolAndIndex(cell, null, default, -1, null);
2051 }
2052
2053 cell.blendingInfo.blendingFactor = 0.0f;
2054 }
2055 else if (supportScenarioBlending)
2056 {
2057 cell.blendingInfo.Prioritize();
2058 // Cell index update is delayed until probe data is loaded
2059 cell.indexInfo.indexUpdated = false;
2060 }
2061
2062 cell.loaded = true;
2063 ClearDebugData();
2064
2065 return true;
2066 }
2067
2068 void UpdateCellIndex(Cell cell)
2069 {
2070 cell.indexInfo.indexUpdated = true;
2071
2072 // Build index
2073 var bricks = cell.data.bricks;
2074 m_Index.AddBricks(cell.indexInfo, bricks, cell.poolInfo.chunkList, ProbeBrickPool.GetChunkSizeInBrickCount(), m_Pool.GetPoolWidth(), m_Pool.GetPoolHeight());
2075
2076 // Update indirection buffer
2077 m_CellIndices.UpdateCell(cell.indexInfo);
2078 }
2079
2080 void ReleaseBricks(Cell cell)
2081 {
2082 if (cell.poolInfo.chunkList.Count == 0)
2083 {
2084 Debug.Log("Tried to release bricks from an empty Cell.");
2085 return;
2086 }
2087
2088 // clean up the index
2089 m_Index.RemoveBricks(cell.indexInfo);
2090 cell.indexInfo.indexUpdated = false;
2091
2092 // clean up the pool
2093 m_Pool.Deallocate(cell.poolInfo.chunkList);
2094
2095 cell.poolInfo.chunkList.Clear();
2096 }
2097
2098 internal void UpdateConstantBuffer(CommandBuffer cmd, ProbeVolumeShadingParameters parameters)
2099 {
2100 float normalBias = parameters.normalBias;
2101 float viewBias = parameters.viewBias;
2102 var leakReductionMode = parameters.leakReductionMode;
2103
2104 if (parameters.scaleBiasByMinDistanceBetweenProbes)
2105 {
2106 normalBias *= MinDistanceBetweenProbes();
2107 viewBias *= MinDistanceBetweenProbes();
2108 }
2109
2110 var indexDim = m_CellIndices.GetGlobalIndirectionDimension();
2111 var poolDim = m_Pool.GetPoolDimensions();
2112 m_CellIndices.GetMinMaxEntry(out Vector3Int minEntry, out Vector3Int _);
2113 var entriesPerCell = m_CellIndices.entriesPerCellDimension;
2114 var skyDirectionWeight = parameters.skyOcclusionShadingDirection ? 1.0f : 0.0f;
2115 var probeOffset = ProbeOffset() + parameters.worldOffset;
2116
2117 ShaderVariablesProbeVolumes shaderVars;
2118 shaderVars._Offset_LayerCount = new Vector4(probeOffset.x, probeOffset.y, probeOffset.z, parameters.regionCount);
2119 shaderVars._MinLoadedCellInEntries_IndirectionEntryDim = new Vector4(minLoadedCellPos.x * entriesPerCell, minLoadedCellPos.y * entriesPerCell, minLoadedCellPos.z * entriesPerCell, GetEntrySize());
2120 shaderVars._MaxLoadedCellInEntries_RcpIndirectionEntryDim = new Vector4((maxLoadedCellPos.x + 1) * entriesPerCell - 1, (maxLoadedCellPos.y + 1) * entriesPerCell - 1, (maxLoadedCellPos.z + 1) * entriesPerCell - 1, 1.0f / GetEntrySize());
2121 shaderVars._PoolDim_MinBrickSize = new Vector4(poolDim.x, poolDim.y, poolDim.z, MinBrickSize());
2122 shaderVars._RcpPoolDim_XY = new Vector4(1.0f / poolDim.x, 1.0f / poolDim.y, 1.0f / poolDim.z, 1.0f / (poolDim.x * poolDim.y));
2123 shaderVars._MinEntryPos_Noise = new Vector4(minEntry.x, minEntry.y, minEntry.z, parameters.samplingNoise);
2124 shaderVars._EntryCount_X_XY_LeakReduction = new uint4((uint)indexDim.x, (uint)indexDim.x * (uint)indexDim.y, (uint)leakReductionMode, 0); // One slot available here
2125 shaderVars._Biases_NormalizationClamp = new Vector4(normalBias, viewBias, parameters.reflNormalizationLowerClamp, parameters.reflNormalizationUpperClamp);
2126 shaderVars._FrameIndex_Weights = new Vector4(parameters.frameIndexForNoise, parameters.weight, parameters.skyOcclusionIntensity, skyDirectionWeight);
2127 shaderVars._ProbeVolumeLayerMask = parameters.regionLayerMasks;
2128
2129 ConstantBuffer.PushGlobal(cmd, shaderVars, m_CBShaderID);
2130 }
2131
2132 void DeinitProbeReferenceVolume()
2133 {
2134 if (m_ProbeReferenceVolumeInit)
2135 {
2136 foreach (var data in perSceneDataList)
2137 AddPendingSceneRemoval(data.sceneGUID);
2138
2139 PerformPendingDeletion();
2140
2141 m_Index.Cleanup();
2142 m_CellIndices.Cleanup();
2143
2144 if (m_SupportGPUStreaming)
2145 {
2146 m_DefragIndex.Cleanup();
2147 m_DefragCellIndices.Cleanup();
2148 }
2149
2150 if (m_Pool != null)
2151 {
2152 m_Pool.Cleanup();
2153 m_BlendingPool.Cleanup();
2154 }
2155
2156 m_TemporaryDataLocation.Cleanup();
2157 m_ProbeReferenceVolumeInit = false;
2158
2159 if (m_CurrentBakingSet != null)
2160 m_CurrentBakingSet.Cleanup();
2161 m_CurrentBakingSet = null;
2162 }
2163 else
2164 {
2165 m_CellIndices?.Cleanup();
2166 m_DefragCellIndices?.Cleanup();
2167 }
2168
2169 ClearDebugData();
2170
2171 Debug.Assert(m_LoadedCells.size == 0);
2172 }
2173
2174 /// <summary>
2175 /// Cleanup loaded data.
2176 /// </summary>
2177 void CleanupLoadedData()
2178 {
2179 UnloadAllCells();
2180 }
2181 }
2182}