A game about forced loneliness, made by TACStudios
1using System.Diagnostics; 2using System.Collections.Generic; 3using UnityEngine.Profiling; 4using UnityEngine.Experimental.Rendering; 5using Cell = UnityEngine.Rendering.ProbeReferenceVolume.Cell; 6using CellStreamingScratchBuffer = UnityEngine.Rendering.ProbeReferenceVolume.CellStreamingScratchBuffer; 7using CellStreamingScratchBufferLayout = UnityEngine.Rendering.ProbeReferenceVolume.CellStreamingScratchBufferLayout; 8 9namespace UnityEngine.Rendering 10{ 11 internal class ProbeBrickPool 12 { 13 internal static readonly int _Out_L0_L1Rx = Shader.PropertyToID("_Out_L0_L1Rx"); 14 internal static readonly int _Out_L1G_L1Ry = Shader.PropertyToID("_Out_L1G_L1Ry"); 15 internal static readonly int _Out_L1B_L1Rz = Shader.PropertyToID("_Out_L1B_L1Rz"); 16 internal static readonly int _Out_Shared = Shader.PropertyToID("_Out_Shared"); 17 internal static readonly int _Out_ProbeOcclusion = Shader.PropertyToID("_Out_ProbeOcclusion"); 18 internal static readonly int _Out_SkyOcclusionL0L1 = Shader.PropertyToID("_Out_SkyOcclusionL0L1"); 19 internal static readonly int _Out_SkyShadingDirectionIndices = Shader.PropertyToID("_Out_SkyShadingDirectionIndices"); 20 internal static readonly int _Out_L2_0 = Shader.PropertyToID("_Out_L2_0"); 21 internal static readonly int _Out_L2_1 = Shader.PropertyToID("_Out_L2_1"); 22 internal static readonly int _Out_L2_2 = Shader.PropertyToID("_Out_L2_2"); 23 internal static readonly int _Out_L2_3 = Shader.PropertyToID("_Out_L2_3"); 24 internal static readonly int _ProbeVolumeScratchBufferLayout = Shader.PropertyToID(nameof(ProbeReferenceVolume.CellStreamingScratchBufferLayout)); 25 internal static readonly int _ProbeVolumeScratchBuffer= Shader.PropertyToID("_ScratchBuffer"); 26 27 internal static int DivRoundUp(int x, int y) => (x + y - 1) / y; 28 29 const int kChunkSizeInBricks = 128; 30 31 [DebuggerDisplay("Chunk ({x}, {y}, {z})")] 32 public struct BrickChunkAlloc 33 { 34 public int x, y, z; 35 36 internal int flattenIndex(int sx, int sy) { return z * (sx * sy) + y * sx + x; } 37 } 38 39 public struct DataLocation 40 { 41 internal Texture TexL0_L1rx; 42 43 internal Texture TexL1_G_ry; 44 internal Texture TexL1_B_rz; 45 46 internal Texture TexL2_0; 47 internal Texture TexL2_1; 48 internal Texture TexL2_2; 49 internal Texture TexL2_3; 50 51 internal Texture TexProbeOcclusion; 52 53 internal Texture TexValidity; 54 internal Texture TexSkyOcclusion; 55 internal Texture TexSkyShadingDirectionIndices; 56 57 internal int width; 58 internal int height; 59 internal int depth; 60 61 internal void Cleanup() 62 { 63 CoreUtils.Destroy(TexL0_L1rx); 64 65 CoreUtils.Destroy(TexL1_G_ry); 66 CoreUtils.Destroy(TexL1_B_rz); 67 68 CoreUtils.Destroy(TexL2_0); 69 CoreUtils.Destroy(TexL2_1); 70 CoreUtils.Destroy(TexL2_2); 71 CoreUtils.Destroy(TexL2_3); 72 73 CoreUtils.Destroy(TexProbeOcclusion); 74 75 CoreUtils.Destroy(TexValidity); 76 CoreUtils.Destroy(TexSkyOcclusion); 77 CoreUtils.Destroy(TexSkyShadingDirectionIndices); 78 79 TexL0_L1rx = null; 80 81 TexL1_G_ry = null; 82 TexL1_B_rz = null; 83 84 TexL2_0 = null; 85 TexL2_1 = null; 86 TexL2_2 = null; 87 TexL2_3 = null; 88 TexProbeOcclusion = null; 89 TexValidity = null; 90 TexSkyOcclusion = null; 91 TexSkyShadingDirectionIndices = null; 92 } 93 } 94 95 internal const int kBrickCellCount = 3; 96 internal const int kBrickProbeCountPerDim = kBrickCellCount + 1; 97 internal const int kBrickProbeCountTotal = kBrickProbeCountPerDim * kBrickProbeCountPerDim * kBrickProbeCountPerDim; 98 internal const int kChunkProbeCountPerDim = kChunkSizeInBricks * kBrickProbeCountPerDim; 99 100 internal int estimatedVMemCost { get; private set; } 101 102 const int kMaxPoolWidth = 1 << 11; // 2048 texels is a d3d11 limit for tex3d in all dimensions 103 104 internal DataLocation m_Pool; // internal to access it from blending pool only 105 BrickChunkAlloc m_NextFreeChunk; 106 Stack<BrickChunkAlloc> m_FreeList; 107 int m_AvailableChunkCount; 108 109 ProbeVolumeSHBands m_SHBands; 110 bool m_ContainsValidity; 111 bool m_ContainsProbeOcclusion; 112 bool m_ContainsRenderingLayers; 113 bool m_ContainsSkyOcclusion; 114 bool m_ContainsSkyShadingDirection; 115 116 static ComputeShader s_DataUploadCS; 117 static int s_DataUploadKernel; 118 static ComputeShader s_DataUploadL2CS; 119 static int s_DataUploadL2Kernel; 120 static LocalKeyword s_DataUpload_Shared; 121 static LocalKeyword s_DataUpload_ProbeOcclusion; 122 static LocalKeyword s_DataUpload_SkyOcclusion; 123 static LocalKeyword s_DataUpload_SkyShadingDirection; 124 125 internal static void Initialize() 126 { 127 if (!SystemInfo.supportsComputeShaders) 128 return; 129 130 s_DataUploadCS = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeRuntimeResources>()?.probeVolumeUploadDataCS; 131 s_DataUploadL2CS = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeRuntimeResources>()?.probeVolumeUploadDataL2CS; 132 133 if (s_DataUploadCS != null) 134 { 135 s_DataUploadKernel = s_DataUploadCS ? s_DataUploadCS.FindKernel("UploadData") : -1; 136 s_DataUpload_Shared = new LocalKeyword(s_DataUploadCS, "PROBE_VOLUMES_SHARED_DATA"); 137 s_DataUpload_ProbeOcclusion = new LocalKeyword(s_DataUploadCS, "PROBE_VOLUMES_PROBE_OCCLUSION"); 138 s_DataUpload_SkyOcclusion = new LocalKeyword(s_DataUploadCS, "PROBE_VOLUMES_SKY_OCCLUSION"); 139 s_DataUpload_SkyShadingDirection = new LocalKeyword(s_DataUploadCS, "PROBE_VOLUMES_SKY_SHADING_DIRECTION"); 140 } 141 142 if (s_DataUploadL2CS != null) 143 { 144 s_DataUploadL2Kernel = s_DataUploadL2CS ? s_DataUploadL2CS.FindKernel("UploadDataL2") : -1; 145 } 146 } 147 148 internal Texture GetValidityTexture() 149 { 150 return m_Pool.TexValidity; 151 } 152 153 internal Texture GetSkyOcclusionTexture() 154 { 155 return m_Pool.TexSkyOcclusion; 156 } 157 158 internal Texture GetSkyShadingDirectionIndicesTexture() 159 { 160 return m_Pool.TexSkyShadingDirectionIndices; 161 } 162 163 internal Texture GetProbeOcclusionTexture() 164 { 165 return m_Pool.TexProbeOcclusion; 166 } 167 168 internal ProbeBrickPool(ProbeVolumeTextureMemoryBudget memoryBudget, ProbeVolumeSHBands shBands, bool allocateValidityData = false, bool allocateRenderingLayerData = false, bool allocateSkyOcclusion = false, bool allocateSkyShadingData = false, bool allocateProbeOcclusionData = false) 169 { 170 Profiler.BeginSample("Create ProbeBrickPool"); 171 m_NextFreeChunk.x = m_NextFreeChunk.y = m_NextFreeChunk.z = 0; 172 173 m_SHBands = shBands; 174 m_ContainsValidity = allocateValidityData; 175 m_ContainsProbeOcclusion = allocateProbeOcclusionData; 176 m_ContainsRenderingLayers = allocateRenderingLayerData; 177 m_ContainsSkyOcclusion = allocateSkyOcclusion; 178 m_ContainsSkyShadingDirection = allocateSkyShadingData; 179 180 m_FreeList = new Stack<BrickChunkAlloc>(256); 181 182 DerivePoolSizeFromBudget(memoryBudget, out int width, out int height, out int depth); 183 AllocatePool(width, height, depth); 184 185 m_AvailableChunkCount = (m_Pool.width / (kChunkSizeInBricks * kBrickProbeCountPerDim)) * (m_Pool.height / kBrickProbeCountPerDim) * (m_Pool.depth / kBrickProbeCountPerDim); 186 187 Profiler.EndSample(); 188 } 189 190 internal void AllocatePool(int width, int height, int depth) 191 { 192 m_Pool = CreateDataLocation(width * height * depth, false, m_SHBands, "APV", true, 193 m_ContainsValidity, m_ContainsRenderingLayers, m_ContainsSkyOcclusion, m_ContainsSkyShadingDirection, m_ContainsProbeOcclusion, out int estimatedCost); 194 estimatedVMemCost = estimatedCost; 195 } 196 197 public int GetRemainingChunkCount() 198 { 199 return m_AvailableChunkCount; 200 } 201 202 internal void EnsureTextureValidity() 203 { 204 // We assume that if a texture is null, all of them are. In any case we reboot them altogether. 205 if (m_Pool.TexL0_L1rx == null) 206 { 207 m_Pool.Cleanup(); 208 AllocatePool(m_Pool.width, m_Pool.height, m_Pool.depth); 209 } 210 } 211 212 internal bool EnsureTextureValidity(bool renderingLayers, bool skyOcclusion, bool skyDirection, bool probeOcclusion) 213 { 214 if (m_ContainsRenderingLayers != renderingLayers || m_ContainsSkyOcclusion != skyOcclusion || m_ContainsSkyShadingDirection != skyDirection || m_ContainsProbeOcclusion != probeOcclusion) 215 { 216 m_Pool.Cleanup(); 217 218 m_ContainsRenderingLayers = renderingLayers; 219 m_ContainsSkyOcclusion = skyOcclusion; 220 m_ContainsSkyShadingDirection = skyDirection; 221 m_ContainsProbeOcclusion = probeOcclusion; 222 AllocatePool(m_Pool.width, m_Pool.height, m_Pool.depth); 223 return false; 224 } 225 return true; 226 } 227 228 internal static int GetChunkSizeInBrickCount() { return kChunkSizeInBricks; } 229 internal static int GetChunkSizeInProbeCount() { return kChunkSizeInBricks * kBrickProbeCountTotal; } 230 231 internal int GetPoolWidth() { return m_Pool.width; } 232 internal int GetPoolHeight() { return m_Pool.height; } 233 internal Vector3Int GetPoolDimensions() { return new Vector3Int(m_Pool.width, m_Pool.height, m_Pool.depth); } 234 internal void GetRuntimeResources(ref ProbeReferenceVolume.RuntimeResources rr) 235 { 236 rr.L0_L1rx = m_Pool.TexL0_L1rx as RenderTexture; 237 238 rr.L1_G_ry = m_Pool.TexL1_G_ry as RenderTexture; 239 rr.L1_B_rz = m_Pool.TexL1_B_rz as RenderTexture; 240 241 rr.L2_0 = m_Pool.TexL2_0 as RenderTexture; 242 rr.L2_1 = m_Pool.TexL2_1 as RenderTexture; 243 rr.L2_2 = m_Pool.TexL2_2 as RenderTexture; 244 rr.L2_3 = m_Pool.TexL2_3 as RenderTexture; 245 246 rr.ProbeOcclusion = m_Pool.TexProbeOcclusion as RenderTexture; 247 248 rr.Validity = m_Pool.TexValidity as RenderTexture; 249 rr.SkyOcclusionL0L1 = m_Pool.TexSkyOcclusion as RenderTexture; 250 rr.SkyShadingDirectionIndices = m_Pool.TexSkyShadingDirectionIndices as RenderTexture; 251 } 252 253 internal void Clear() 254 { 255 m_FreeList.Clear(); 256 m_NextFreeChunk.x = m_NextFreeChunk.y = m_NextFreeChunk.z = 0; 257 } 258 259 internal static int GetChunkCount(int brickCount) 260 { 261 int chunkSize = kChunkSizeInBricks; 262 return (brickCount + chunkSize - 1) / chunkSize; 263 } 264 265 internal bool Allocate(int numberOfBrickChunks, List<BrickChunkAlloc> outAllocations, bool ignoreErrorLog) 266 { 267 while (m_FreeList.Count > 0 && numberOfBrickChunks > 0) 268 { 269 outAllocations.Add(m_FreeList.Pop()); 270 numberOfBrickChunks--; 271 m_AvailableChunkCount--; 272 } 273 274 for (uint i = 0; i < numberOfBrickChunks; i++) 275 { 276 if (m_NextFreeChunk.z >= m_Pool.depth) 277 { 278 // During baking we know we can hit this when trying to do dilation of all cells at the same time. 279 // We don't want controlled error message spam during baking so we ignore it. 280 // In theory this should never happen with proper streaming/defrag but we keep the message just in case otherwise. 281 if (!ignoreErrorLog) 282 Debug.LogError("Cannot allocate more brick chunks, probe volume brick pool is full."); 283 284 outAllocations.Clear(); 285 return false; // failure case, pool is full 286 } 287 288 outAllocations.Add(m_NextFreeChunk); 289 m_AvailableChunkCount--; 290 291 m_NextFreeChunk.x += kChunkSizeInBricks * kBrickProbeCountPerDim; 292 if (m_NextFreeChunk.x >= m_Pool.width) 293 { 294 m_NextFreeChunk.x = 0; 295 m_NextFreeChunk.y += kBrickProbeCountPerDim; 296 if (m_NextFreeChunk.y >= m_Pool.height) 297 { 298 m_NextFreeChunk.y = 0; 299 m_NextFreeChunk.z += kBrickProbeCountPerDim; 300 } 301 } 302 } 303 304 return true; 305 } 306 307 internal void Deallocate(List<BrickChunkAlloc> allocations) 308 { 309 m_AvailableChunkCount += allocations.Count; 310 311 foreach (var brick in allocations) 312 m_FreeList.Push(brick); 313 } 314 315 internal void Update(DataLocation source, List<BrickChunkAlloc> srcLocations, List<BrickChunkAlloc> dstLocations, int destStartIndex, ProbeVolumeSHBands bands) 316 { 317 for (int i = 0; i < srcLocations.Count; i++) 318 { 319 BrickChunkAlloc src = srcLocations[i]; 320 BrickChunkAlloc dst = dstLocations[destStartIndex + i]; 321 322 for (int j = 0; j < kBrickProbeCountPerDim; j++) 323 { 324 int width = Mathf.Min(kChunkSizeInBricks * kBrickProbeCountPerDim, source.width - src.x); 325 Graphics.CopyTexture(source.TexL0_L1rx, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL0_L1rx, dst.z + j, 0, dst.x, dst.y); 326 327 Graphics.CopyTexture(source.TexL1_G_ry, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL1_G_ry, dst.z + j, 0, dst.x, dst.y); 328 Graphics.CopyTexture(source.TexL1_B_rz, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL1_B_rz, dst.z + j, 0, dst.x, dst.y); 329 330 if (m_ContainsValidity) 331 Graphics.CopyTexture(source.TexValidity, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexValidity, dst.z + j, 0, dst.x, dst.y); 332 333 if (m_ContainsSkyOcclusion) 334 { 335 Graphics.CopyTexture(source.TexSkyOcclusion, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexSkyOcclusion, dst.z + j, 0, dst.x, dst.y); 336 if (m_ContainsSkyShadingDirection) 337 { 338 Graphics.CopyTexture(source.TexSkyShadingDirectionIndices, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexSkyShadingDirectionIndices, dst.z + j, 0, dst.x, dst.y); 339 } 340 } 341 342 if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2) 343 { 344 Graphics.CopyTexture(source.TexL2_0, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_0, dst.z + j, 0, dst.x, dst.y); 345 Graphics.CopyTexture(source.TexL2_1, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_1, dst.z + j, 0, dst.x, dst.y); 346 Graphics.CopyTexture(source.TexL2_2, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_2, dst.z + j, 0, dst.x, dst.y); 347 Graphics.CopyTexture(source.TexL2_3, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_3, dst.z + j, 0, dst.x, dst.y); 348 } 349 350 if (m_ContainsProbeOcclusion) 351 { 352 Graphics.CopyTexture(source.TexProbeOcclusion, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexProbeOcclusion, dst.z + j, 0, dst.x, dst.y); 353 } 354 } 355 } 356 } 357 358 internal void Update(CommandBuffer cmd, CellStreamingScratchBuffer dataBuffer, CellStreamingScratchBufferLayout layout, 359 List<BrickChunkAlloc> dstLocations, bool updateSharedData, Texture validityTexture, ProbeVolumeSHBands bands, 360 bool skyOcclusion, Texture skyOcclusionTexture, bool skyShadingDirections, Texture skyShadingDirectionsTexture, bool probeOcclusion) 361 { 362 using (new ProfilingScope(cmd, ProfilingSampler.Get(CoreProfileId.APVDiskStreamingUpdatePool))) 363 { 364 int chunkCount = dstLocations.Count; 365 366 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_L0_L1Rx, m_Pool.TexL0_L1rx); 367 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_L1G_L1Ry, m_Pool.TexL1_G_ry); 368 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_L1B_L1Rz, m_Pool.TexL1_B_rz); 369 370 if (updateSharedData) 371 { 372 cmd.EnableKeyword(s_DataUploadCS, s_DataUpload_Shared); 373 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_Shared, validityTexture); 374 375 if (skyOcclusion) 376 { 377 cmd.EnableKeyword(s_DataUploadCS, s_DataUpload_SkyOcclusion); 378 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_SkyOcclusionL0L1, skyOcclusionTexture); 379 if (skyShadingDirections) 380 { 381 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_SkyShadingDirectionIndices, skyShadingDirectionsTexture); 382 cmd.EnableKeyword(s_DataUploadCS, s_DataUpload_SkyShadingDirection); 383 } 384 else 385 cmd.DisableKeyword(s_DataUploadCS, s_DataUpload_SkyShadingDirection); 386 } 387 } 388 else 389 { 390 cmd.DisableKeyword(s_DataUploadCS, s_DataUpload_Shared); 391 cmd.DisableKeyword(s_DataUploadCS, s_DataUpload_SkyOcclusion); 392 cmd.DisableKeyword(s_DataUploadCS, s_DataUpload_SkyShadingDirection); 393 } 394 395 if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2) 396 { 397 cmd.SetComputeTextureParam(s_DataUploadL2CS, s_DataUploadL2Kernel, _Out_L2_0, m_Pool.TexL2_0); 398 cmd.SetComputeTextureParam(s_DataUploadL2CS, s_DataUploadL2Kernel, _Out_L2_1, m_Pool.TexL2_1); 399 cmd.SetComputeTextureParam(s_DataUploadL2CS, s_DataUploadL2Kernel, _Out_L2_2, m_Pool.TexL2_2); 400 cmd.SetComputeTextureParam(s_DataUploadL2CS, s_DataUploadL2Kernel, _Out_L2_3, m_Pool.TexL2_3); 401 } 402 403 if (probeOcclusion) 404 { 405 cmd.EnableKeyword(s_DataUploadCS, s_DataUpload_ProbeOcclusion); 406 cmd.SetComputeTextureParam(s_DataUploadCS, s_DataUploadKernel, _Out_ProbeOcclusion, m_Pool.TexProbeOcclusion); 407 } 408 else 409 { 410 cmd.DisableKeyword(s_DataUploadCS, s_DataUpload_ProbeOcclusion); 411 } 412 413 const int numthreads = 64; 414 const int probePerThread = 4; // We can upload 4 probes per thread in the current shader. 415 int threadX = DivRoundUp(kChunkSizeInBricks * kBrickProbeCountTotal / probePerThread, numthreads); 416 417 ConstantBuffer.Push(cmd, layout, s_DataUploadCS, _ProbeVolumeScratchBufferLayout); 418 cmd.SetComputeBufferParam(s_DataUploadCS, s_DataUploadKernel, _ProbeVolumeScratchBuffer, dataBuffer.buffer); 419 cmd.DispatchCompute(s_DataUploadCS, s_DataUploadKernel, threadX, 1, chunkCount); 420 421 if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2) 422 { 423 ConstantBuffer.Push(cmd, layout, s_DataUploadL2CS, _ProbeVolumeScratchBufferLayout); 424 cmd.SetComputeBufferParam(s_DataUploadL2CS, s_DataUploadL2Kernel, _ProbeVolumeScratchBuffer, dataBuffer.buffer); 425 cmd.DispatchCompute(s_DataUploadL2CS, s_DataUploadL2Kernel, threadX, 1, chunkCount); 426 } 427 } 428 } 429 430 internal void UpdateValidity(DataLocation source, List<BrickChunkAlloc> srcLocations, List<BrickChunkAlloc> dstLocations, int destStartIndex) 431 { 432 Debug.Assert(m_ContainsValidity); 433 434 for (int i = 0; i < srcLocations.Count; i++) 435 { 436 BrickChunkAlloc src = srcLocations[i]; 437 BrickChunkAlloc dst = dstLocations[destStartIndex + i]; 438 439 for (int j = 0; j < kBrickProbeCountPerDim; j++) 440 { 441 int width = Mathf.Min(kChunkSizeInBricks * kBrickProbeCountPerDim, source.width - src.x); 442 Graphics.CopyTexture(source.TexValidity, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexValidity, dst.z + j, 0, dst.x, dst.y); 443 } 444 } 445 } 446 447 internal static Vector3Int ProbeCountToDataLocSize(int numProbes) 448 { 449 Debug.Assert(numProbes != 0); 450 Debug.Assert(numProbes % kBrickProbeCountTotal == 0); 451 452 int numBricks = numProbes / kBrickProbeCountTotal; 453 int poolWidth = kMaxPoolWidth / kBrickProbeCountPerDim; 454 455 int width, height, depth; 456 depth = (numBricks + poolWidth * poolWidth - 1) / (poolWidth * poolWidth); 457 if (depth > 1) 458 width = height = poolWidth; 459 else 460 { 461 height = (numBricks + poolWidth - 1) / poolWidth; 462 if (height > 1) 463 width = poolWidth; 464 else 465 width = numBricks; 466 } 467 468 width *= kBrickProbeCountPerDim; 469 height *= kBrickProbeCountPerDim; 470 depth *= kBrickProbeCountPerDim; 471 472 return new Vector3Int(width, height, depth); 473 } 474 475 static int EstimateMemoryCost(int width, int height, int depth, GraphicsFormat format) 476 { 477 int elementSize = format == GraphicsFormat.R16G16B16A16_SFloat ? 8 : 478 format == GraphicsFormat.R8G8B8A8_UNorm ? 4 : 1; 479 return (width * height * depth) * elementSize; 480 } 481 482 // Only computes the cost of textures allocated by the blending pool 483 internal static int EstimateMemoryCostForBlending(ProbeVolumeTextureMemoryBudget memoryBudget, bool compressed, ProbeVolumeSHBands bands) 484 { 485 if (memoryBudget == 0) 486 return 0; 487 488 DerivePoolSizeFromBudget(memoryBudget, out int width, out int height, out int depth); 489 Vector3Int locSize = ProbeCountToDataLocSize(width * height * depth); 490 width = locSize.x; 491 height = locSize.y; 492 depth = locSize.z; 493 494 int allocatedBytes = 0; 495 var L0Format = GraphicsFormat.R16G16B16A16_SFloat; 496 var L1L2Format = compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm; 497 498 allocatedBytes += EstimateMemoryCost(width, height, depth, L0Format); 499 allocatedBytes += EstimateMemoryCost(width, height, depth, L1L2Format) * 2; 500 501 if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2) 502 allocatedBytes += EstimateMemoryCost(width, height, depth, L1L2Format) * 3; 503 504 return allocatedBytes; 505 } 506 507 public static Texture CreateDataTexture(int width, int height, int depth, GraphicsFormat format, string name, bool allocateRendertexture, ref int allocatedBytes) 508 { 509 allocatedBytes += EstimateMemoryCost(width, height, depth, format); 510 511 Texture texture; 512 if (allocateRendertexture) 513 { 514 texture = new RenderTexture(new RenderTextureDescriptor() 515 { 516 width = width, 517 height = height, 518 volumeDepth = depth, 519 graphicsFormat = format, 520 mipCount = 1, 521 enableRandomWrite = SystemInfo.supportsComputeShaders, 522 dimension = TextureDimension.Tex3D, 523 msaaSamples = 1, 524 }); 525 } 526 else 527 texture = new Texture3D(width, height, depth, format, TextureCreationFlags.None, 1); 528 529 texture.hideFlags = HideFlags.HideAndDontSave; 530 texture.name = name; 531 532 if (allocateRendertexture) 533 (texture as RenderTexture).Create(); 534 return texture; 535 } 536 537 public static DataLocation CreateDataLocation(int numProbes, bool compressed, ProbeVolumeSHBands bands, string name, bool allocateRendertexture, 538 bool allocateValidityData, bool allocateRenderingLayers, bool allocateSkyOcclusionData, bool allocateSkyShadingDirectionData, bool allocateProbeOcclusionData, out int allocatedBytes) 539 { 540 Vector3Int locSize = ProbeCountToDataLocSize(numProbes); 541 int width = locSize.x; 542 int height = locSize.y; 543 int depth = locSize.z; 544 545 DataLocation loc; 546 var L0Format = GraphicsFormat.R16G16B16A16_SFloat; 547 var L1L2Format = compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm; 548 549 var ValidityFormat = allocateRenderingLayers ? 550 // for 32 bits we use a float format but it's an uint 551 GraphicsFormat.R32_SFloat : 552 // NOTE: Platforms that do not support Sample nor LoadStore for R8_UNorm need to fallback to RGBA8_UNorm since that format should be supported for both (e.g. GLES3.x) 553 SystemInfo.IsFormatSupported(GraphicsFormat.R8_UNorm, GraphicsFormatUsage.Sample | GraphicsFormatUsage.LoadStore) ? GraphicsFormat.R8_UNorm : GraphicsFormat.R8G8B8A8_UNorm; 554 555 allocatedBytes = 0; 556 loc.TexL0_L1rx = CreateDataTexture(width, height, depth, L0Format, $"{name}_TexL0_L1rx", allocateRendertexture, ref allocatedBytes); 557 loc.TexL1_G_ry = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL1_G_ry", allocateRendertexture, ref allocatedBytes); 558 loc.TexL1_B_rz = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL1_B_rz", allocateRendertexture, ref allocatedBytes); 559 560 if (allocateValidityData) 561 loc.TexValidity = CreateDataTexture(width, height, depth, ValidityFormat, $"{name}_Validity", allocateRendertexture, ref allocatedBytes); 562 else 563 loc.TexValidity = null; 564 565 if (allocateSkyOcclusionData) 566 loc.TexSkyOcclusion = CreateDataTexture(width, height, depth, GraphicsFormat.R16G16B16A16_SFloat, $"{name}_SkyOcclusion", allocateRendertexture, ref allocatedBytes); 567 else 568 loc.TexSkyOcclusion = null; 569 570 if (allocateSkyShadingDirectionData) 571 loc.TexSkyShadingDirectionIndices = CreateDataTexture(width, height, depth, GraphicsFormat.R8_UNorm, $"{name}_SkyShadingDirectionIndices", allocateRendertexture, ref allocatedBytes); 572 else 573 loc.TexSkyShadingDirectionIndices = null; 574 575 if (allocateProbeOcclusionData) 576 loc.TexProbeOcclusion = CreateDataTexture(width, height, depth, GraphicsFormat.R8G8B8A8_UNorm, $"{name}_ProbeOcclusion", allocateRendertexture, ref allocatedBytes); 577 else 578 loc.TexProbeOcclusion = null; 579 580 if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2) 581 { 582 loc.TexL2_0 = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL2_0", allocateRendertexture, ref allocatedBytes); 583 loc.TexL2_1 = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL2_1", allocateRendertexture, ref allocatedBytes); 584 loc.TexL2_2 = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL2_2", allocateRendertexture, ref allocatedBytes); 585 loc.TexL2_3 = CreateDataTexture(width, height, depth, L1L2Format, $"{name}_TexL2_3", allocateRendertexture, ref allocatedBytes); 586 } 587 else 588 { 589 loc.TexL2_0 = null; 590 loc.TexL2_1 = null; 591 loc.TexL2_2 = null; 592 loc.TexL2_3 = null; 593 } 594 595 loc.width = width; 596 loc.height = height; 597 loc.depth = depth; 598 599 return loc; 600 } 601 602 static void DerivePoolSizeFromBudget(ProbeVolumeTextureMemoryBudget memoryBudget, out int width, out int height, out int depth) 603 { 604 // TODO: This is fairly simplistic for now and relies on the enum to have the value set to the desired numbers, 605 // might change the heuristic later on. 606 width = (int)memoryBudget; 607 height = (int)memoryBudget; 608 depth = kBrickProbeCountPerDim; 609 } 610 611 internal void Cleanup() 612 { 613 m_Pool.Cleanup(); 614 } 615 } 616 617 internal class ProbeBrickBlendingPool 618 { 619 static ComputeShader stateBlendShader; 620 static int scenarioBlendingKernel = -1; 621 622 static readonly int _PoolDim_LerpFactor = Shader.PropertyToID("_PoolDim_LerpFactor"); 623 static readonly int _ChunkList = Shader.PropertyToID("_ChunkList"); 624 625 static readonly int _State0_L0_L1Rx = Shader.PropertyToID("_State0_L0_L1Rx"); 626 static readonly int _State0_L1G_L1Ry = Shader.PropertyToID("_State0_L1G_L1Ry"); 627 static readonly int _State0_L1B_L1Rz = Shader.PropertyToID("_State0_L1B_L1Rz"); 628 static readonly int _State0_L2_0 = Shader.PropertyToID("_State0_L2_0"); 629 static readonly int _State0_L2_1 = Shader.PropertyToID("_State0_L2_1"); 630 static readonly int _State0_L2_2 = Shader.PropertyToID("_State0_L2_2"); 631 static readonly int _State0_L2_3 = Shader.PropertyToID("_State0_L2_3"); 632 static readonly int _State0_ProbeOcclusion = Shader.PropertyToID("_State0_ProbeOcclusion"); 633 634 static readonly int _State1_L0_L1Rx = Shader.PropertyToID("_State1_L0_L1Rx"); 635 static readonly int _State1_L1G_L1Ry = Shader.PropertyToID("_State1_L1G_L1Ry"); 636 static readonly int _State1_L1B_L1Rz = Shader.PropertyToID("_State1_L1B_L1Rz"); 637 static readonly int _State1_L2_0 = Shader.PropertyToID("_State1_L2_0"); 638 static readonly int _State1_L2_1 = Shader.PropertyToID("_State1_L2_1"); 639 static readonly int _State1_L2_2 = Shader.PropertyToID("_State1_L2_2"); 640 static readonly int _State1_L2_3 = Shader.PropertyToID("_State1_L2_3"); 641 static readonly int _State1_ProbeOcclusion = Shader.PropertyToID("_State1_ProbeOcclusion"); 642 643 internal static void Initialize() 644 { 645 if (SystemInfo.supportsComputeShaders) 646 { 647 stateBlendShader = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeRuntimeResources>()?.probeVolumeBlendStatesCS; 648 scenarioBlendingKernel = stateBlendShader ? stateBlendShader.FindKernel("BlendScenarios") : -1; 649 } 650 } 651 652 Vector4[] m_ChunkList; 653 int m_MappedChunks; 654 655 ProbeBrickPool m_State0, m_State1; 656 ProbeVolumeTextureMemoryBudget m_MemoryBudget; 657 ProbeVolumeSHBands m_ShBands; 658 bool m_ProbeOcclusion; 659 660 internal bool isAllocated => m_State0 != null; 661 internal int estimatedVMemCost 662 { 663 get 664 { 665 if (!ProbeReferenceVolume.instance.supportScenarioBlending) 666 return 0; 667 if (isAllocated) 668 return m_State0.estimatedVMemCost + m_State1.estimatedVMemCost; 669 return ProbeBrickPool.EstimateMemoryCostForBlending(m_MemoryBudget, false, m_ShBands) * 2; 670 } 671 } 672 673 internal int GetPoolWidth() { return m_State0.m_Pool.width; } 674 internal int GetPoolHeight() { return m_State0.m_Pool.height; } 675 internal int GetPoolDepth() { return m_State0.m_Pool.depth; } 676 677 internal ProbeBrickBlendingPool(ProbeVolumeBlendingTextureMemoryBudget memoryBudget, ProbeVolumeSHBands shBands, bool probeOcclusion) 678 { 679 // Casting to other memory budget struct works cause it's casted to int in the end anyway 680 m_MemoryBudget = (ProbeVolumeTextureMemoryBudget)memoryBudget; 681 m_ShBands = shBands; 682 m_ProbeOcclusion = probeOcclusion; 683 } 684 685 internal void AllocateResourcesIfNeeded() 686 { 687 if (isAllocated) 688 return; 689 690 m_State0 = new ProbeBrickPool(m_MemoryBudget, m_ShBands, allocateProbeOcclusionData: m_ProbeOcclusion); 691 m_State1 = new ProbeBrickPool(m_MemoryBudget, m_ShBands, allocateProbeOcclusionData: m_ProbeOcclusion); 692 693 int maxAvailablebrickCount = (GetPoolWidth() / ProbeBrickPool.kChunkProbeCountPerDim) 694 * (GetPoolHeight() / ProbeBrickPool.kBrickProbeCountPerDim) 695 * (GetPoolDepth() / ProbeBrickPool.kBrickProbeCountPerDim); 696 697 m_ChunkList = new Vector4[maxAvailablebrickCount]; 698 m_MappedChunks = 0; 699 } 700 701 internal void Update(ProbeBrickPool.DataLocation source, List<ProbeBrickPool.BrickChunkAlloc> srcLocations, List<ProbeBrickPool.BrickChunkAlloc> dstLocations, int destStartIndex, ProbeVolumeSHBands bands, int state) 702 { 703 (state == 0 ? m_State0 : m_State1).Update(source, srcLocations, dstLocations, destStartIndex, bands); 704 } 705 706 internal void Update(CommandBuffer cmd, CellStreamingScratchBuffer dataBuffer, CellStreamingScratchBufferLayout layout, 707 List<ProbeBrickPool.BrickChunkAlloc> dstLocations, ProbeVolumeSHBands bands, int state, Texture validityTexture, 708 bool skyOcclusion, Texture skyOcclusionTexture, bool skyShadingDirections, Texture skyShadingDirectionsTexture, bool probeOcclusion) 709 { 710 bool updateShared = state == 0 ? true : false; 711 712 (state == 0 ? m_State0 : m_State1).Update(cmd, dataBuffer, layout, dstLocations, 713 updateShared, validityTexture, bands, updateShared && skyOcclusion, skyOcclusionTexture, 714 updateShared && skyShadingDirections, skyShadingDirectionsTexture, probeOcclusion); 715 } 716 717 internal void PerformBlending(CommandBuffer cmd, float factor, ProbeBrickPool dstPool) 718 { 719 if (m_MappedChunks == 0) 720 return; 721 722 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L0_L1Rx, m_State0.m_Pool.TexL0_L1rx); 723 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L1G_L1Ry, m_State0.m_Pool.TexL1_G_ry); 724 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L1B_L1Rz, m_State0.m_Pool.TexL1_B_rz); 725 726 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L0_L1Rx, m_State1.m_Pool.TexL0_L1rx); 727 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L1G_L1Ry, m_State1.m_Pool.TexL1_G_ry); 728 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L1B_L1Rz, m_State1.m_Pool.TexL1_B_rz); 729 730 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L0_L1Rx, dstPool.m_Pool.TexL0_L1rx); 731 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L1G_L1Ry, dstPool.m_Pool.TexL1_G_ry); 732 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L1B_L1Rz, dstPool.m_Pool.TexL1_B_rz); 733 734 if (m_ShBands == ProbeVolumeSHBands.SphericalHarmonicsL2) 735 { 736 stateBlendShader.EnableKeyword("PROBE_VOLUMES_L2"); 737 738 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L2_0, m_State0.m_Pool.TexL2_0); 739 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L2_1, m_State0.m_Pool.TexL2_1); 740 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L2_2, m_State0.m_Pool.TexL2_2); 741 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_L2_3, m_State0.m_Pool.TexL2_3); 742 743 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L2_0, m_State1.m_Pool.TexL2_0); 744 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L2_1, m_State1.m_Pool.TexL2_1); 745 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L2_2, m_State1.m_Pool.TexL2_2); 746 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_L2_3, m_State1.m_Pool.TexL2_3); 747 748 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L2_0, dstPool.m_Pool.TexL2_0); 749 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L2_1, dstPool.m_Pool.TexL2_1); 750 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L2_2, dstPool.m_Pool.TexL2_2); 751 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_L2_3, dstPool.m_Pool.TexL2_3); 752 } 753 else 754 stateBlendShader.DisableKeyword("PROBE_VOLUMES_L2"); 755 756 if (m_ProbeOcclusion) 757 { 758 stateBlendShader.EnableKeyword("USE_APV_PROBE_OCCLUSION"); 759 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State0_ProbeOcclusion, m_State0.m_Pool.TexProbeOcclusion); 760 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, _State1_ProbeOcclusion, m_State1.m_Pool.TexProbeOcclusion); 761 cmd.SetComputeTextureParam(stateBlendShader, scenarioBlendingKernel, ProbeBrickPool._Out_ProbeOcclusion, dstPool.m_Pool.TexProbeOcclusion); 762 } 763 else 764 stateBlendShader.DisableKeyword("USE_APV_PROBE_OCCLUSION"); 765 766 var poolDim_LerpFactor = new Vector4(dstPool.GetPoolWidth(), dstPool.GetPoolHeight(), factor, 0.0f); 767 768 const int numthreads = 4; 769 int threadX = ProbeBrickPool.DivRoundUp(ProbeBrickPool.kChunkProbeCountPerDim, numthreads); 770 int threadY = ProbeBrickPool.DivRoundUp(ProbeBrickPool.kBrickProbeCountPerDim, numthreads); 771 int threadZ = ProbeBrickPool.DivRoundUp(ProbeBrickPool.kBrickProbeCountPerDim, numthreads); 772 773 cmd.SetComputeVectorArrayParam(stateBlendShader, _ChunkList, m_ChunkList); 774 cmd.SetComputeVectorParam(stateBlendShader, _PoolDim_LerpFactor, poolDim_LerpFactor); 775 cmd.DispatchCompute(stateBlendShader, scenarioBlendingKernel, threadX, threadY, threadZ * m_MappedChunks); 776 m_MappedChunks = 0; 777 } 778 779 internal void BlendChunks(Cell cell, ProbeBrickPool dstPool) 780 { 781 for (int c = 0; c < cell.blendingInfo.chunkList.Count; c++) 782 { 783 var chunk = cell.blendingInfo.chunkList[c]; 784 int dst = cell.poolInfo.chunkList[c].flattenIndex(dstPool.GetPoolWidth(), dstPool.GetPoolHeight()); 785 786 m_ChunkList[m_MappedChunks++] = new Vector4(chunk.x, chunk.y, chunk.z, dst); 787 } 788 } 789 790 internal void Clear() 791 => m_State0?.Clear(); 792 793 internal bool Allocate(int numberOfBrickChunks, List<ProbeBrickPool.BrickChunkAlloc> outAllocations) 794 { 795 AllocateResourcesIfNeeded(); 796 if (numberOfBrickChunks > m_State0.GetRemainingChunkCount()) 797 return false; 798 799 return m_State0.Allocate(numberOfBrickChunks, outAllocations, false); 800 } 801 802 internal void Deallocate(List<ProbeBrickPool.BrickChunkAlloc> allocations) 803 { 804 if (allocations.Count == 0) 805 return; 806 807 m_State0.Deallocate(allocations); 808 } 809 810 internal void EnsureTextureValidity() 811 { 812 if (isAllocated) 813 { 814 m_State0.EnsureTextureValidity(); 815 m_State1.EnsureTextureValidity(); 816 } 817 } 818 819 internal void Cleanup() 820 { 821 if (isAllocated) 822 { 823 m_State0.Cleanup(); 824 m_State1.Cleanup(); 825 } 826 } 827 } 828}