A game about forced loneliness, made by TACStudios
at master 2182 lines 94 kB view raw
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}