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}