A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.Assertions;
3using Unity.Collections;
4using Unity.Jobs;
5using Unity.Collections.LowLevel.Unsafe;
6using Unity.Burst;
7using Unity.Mathematics;
8
9namespace UnityEngine.Rendering
10{
11 internal unsafe struct LODGroupData
12 {
13 public const int k_MaxLODLevelsCount = 8;
14
15 public bool valid;
16 public int lodCount;
17 public int rendererCount;
18 public fixed float screenRelativeTransitionHeights[k_MaxLODLevelsCount];
19 public fixed float fadeTransitionWidth[k_MaxLODLevelsCount];
20 }
21
22 internal unsafe struct LODGroupCullingData
23 {
24 public float3 worldSpaceReferencePoint;
25 public int lodCount;
26 public fixed float sqrDistances[LODGroupData.k_MaxLODLevelsCount]; // we use square distance to get rid of a sqrt in gpu culling..
27 public fixed float transitionDistances[LODGroupData.k_MaxLODLevelsCount]; // todo - make this a separate data struct (CPUOnly, as we do not support dithering on GPU..)
28 public float worldSpaceSize;// SpeedTree crossfade.
29 public fixed bool percentageFlags[LODGroupData.k_MaxLODLevelsCount];// SpeedTree crossfade.
30 }
31
32 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
33 internal struct UpdateLODGroupTransformJob : IJobParallelFor
34 {
35 public const int k_BatchSize = 256;
36
37 [ReadOnly] public NativeParallelHashMap<int, GPUInstanceIndex> lodGroupDataHash;
38 [ReadOnly] public NativeArray<int> lodGroupIDs;
39 [ReadOnly] public NativeArray<Vector3> worldSpaceReferencePoints;
40 [ReadOnly] public NativeArray<float> worldSpaceSizes;
41 [ReadOnly] public bool requiresGPUUpload;
42 [ReadOnly] public bool supportDitheringCrossFade;
43
44 [NativeDisableContainerSafetyRestriction, NoAlias, ReadOnly] public NativeList<LODGroupData> lodGroupData;
45
46 [NativeDisableContainerSafetyRestriction, NoAlias, WriteOnly] public NativeList<LODGroupCullingData> lodGroupCullingData;
47
48 [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 atomicUpdateCount;
49
50 public unsafe void Execute(int index)
51 {
52 int lodGroupID = lodGroupIDs[index];
53
54 if (lodGroupDataHash.TryGetValue(lodGroupID, out var lodGroupInstance))
55 {
56 var worldSpaceSize = worldSpaceSizes[index];
57
58 LODGroupData* lodGroup = (LODGroupData*)lodGroupData.GetUnsafePtr() + lodGroupInstance.index;
59 LODGroupCullingData* lodGroupTransformResult = (LODGroupCullingData*)lodGroupCullingData.GetUnsafePtr() + lodGroupInstance.index;
60 lodGroupTransformResult->worldSpaceSize = worldSpaceSize;
61 lodGroupTransformResult->worldSpaceReferencePoint = worldSpaceReferencePoints[index];
62
63 for (int i = 0; i < lodGroup->lodCount; ++i)
64 {
65 float lodHeight = lodGroup->screenRelativeTransitionHeights[i];
66
67 var lodDist = LODGroupRenderingUtils.CalculateLODDistance(lodHeight, worldSpaceSize);
68 lodGroupTransformResult->sqrDistances[i] = lodDist * lodDist;
69
70 if (supportDitheringCrossFade && !lodGroupTransformResult->percentageFlags[i])
71 {
72 float prevLODHeight = i != 0 ? lodGroup->screenRelativeTransitionHeights[i - 1] : 1.0f;
73 float transitionHeight = lodHeight + lodGroup->fadeTransitionWidth[i] * (prevLODHeight - lodHeight);
74 var transitionDistance = lodDist - LODGroupRenderingUtils.CalculateLODDistance(transitionHeight, worldSpaceSize);
75 transitionDistance = Mathf.Max(0.0f, transitionDistance);
76 lodGroupTransformResult->transitionDistances[i] = transitionDistance;
77 }
78 else
79 {
80 lodGroupTransformResult->transitionDistances[i] = 0f;
81 }
82
83 }
84 }
85 }
86 }
87
88 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
89 internal unsafe struct AllocateOrGetLODGroupDataInstancesJob : IJob
90 {
91 [ReadOnly] public NativeArray<int> lodGroupsID;
92
93 public NativeList<LODGroupData> lodGroupsData;
94 public NativeList<LODGroupCullingData> lodGroupCullingData;
95 public NativeParallelHashMap<int, GPUInstanceIndex> lodGroupDataHash;
96 public NativeList<GPUInstanceIndex> freeLODGroupDataHandles;
97
98 [WriteOnly] public NativeArray<GPUInstanceIndex> lodGroupInstances;
99
100 [NativeDisableUnsafePtrRestriction] public int* previousRendererCount;
101
102 public void Execute()
103 {
104 int freeHandlesCount = freeLODGroupDataHandles.Length;
105 int lodDataLength = lodGroupsData.Length;
106
107 for (int i = 0; i < lodGroupsID.Length; ++i)
108 {
109 int lodGroupID = lodGroupsID[i];
110
111 if (!lodGroupDataHash.TryGetValue(lodGroupID, out var lodGroupInstance))
112 {
113 if (freeHandlesCount == 0)
114 lodGroupInstance = new GPUInstanceIndex() { index = lodDataLength++ };
115 else
116 lodGroupInstance = freeLODGroupDataHandles[--freeHandlesCount];
117
118 lodGroupDataHash.TryAdd(lodGroupID, lodGroupInstance);
119 }
120 else
121 {
122 *previousRendererCount += lodGroupsData.ElementAt(lodGroupInstance.index).rendererCount;
123 }
124
125 lodGroupInstances[i] = lodGroupInstance;
126 }
127
128 freeLODGroupDataHandles.ResizeUninitialized(freeHandlesCount);
129 lodGroupsData.ResizeUninitialized(lodDataLength);
130 lodGroupCullingData.ResizeUninitialized(lodDataLength);
131 }
132 }
133
134 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
135 internal unsafe struct UpdateLODGroupDataJob : IJobParallelFor
136 {
137 public const int k_BatchSize = 256;
138
139 [ReadOnly] public NativeArray<GPUInstanceIndex> lodGroupInstances;
140 [ReadOnly] public GPUDrivenLODGroupData inputData;
141 [ReadOnly] public bool supportDitheringCrossFade;
142
143 public NativeArray<LODGroupData> lodGroupsData;
144 public NativeArray<LODGroupCullingData> lodGroupsCullingData;
145
146 [NativeDisableUnsafePtrRestriction] public UnsafeAtomicCounter32 rendererCount;
147
148 public void Execute(int index)
149 {
150 var lodGroupInstance = lodGroupInstances[index];
151 var fadeMode = inputData.fadeMode[index];
152 var lodOffset = inputData.lodOffset[index];
153 var lodCount = inputData.lodCount[index];
154 var renderersCount = inputData.renderersCount[index];
155 var worldReferencePoint = inputData.worldSpaceReferencePoint[index];
156 var worldSpaceSize = inputData.worldSpaceSize[index];
157 var lastLODIsBillboard = inputData.lastLODIsBillboard[index];
158 var useDitheringCrossFade = fadeMode != LODFadeMode.None && supportDitheringCrossFade;
159 var useSpeedTreeCrossFade = fadeMode == LODFadeMode.SpeedTree;
160
161 LODGroupData* lodGroupData = (LODGroupData*)lodGroupsData.GetUnsafePtr() + lodGroupInstance.index;
162 LODGroupCullingData* lodGroupCullingData = (LODGroupCullingData*)lodGroupsCullingData.GetUnsafePtr() + lodGroupInstance.index;
163
164 lodGroupData->valid = true;
165 lodGroupData->lodCount = lodCount;
166 lodGroupData->rendererCount = useDitheringCrossFade ? renderersCount : 0;
167 lodGroupCullingData->worldSpaceSize = worldSpaceSize;
168 lodGroupCullingData->worldSpaceReferencePoint = worldReferencePoint;
169 lodGroupCullingData->lodCount = lodCount;
170
171 rendererCount.Add(lodGroupData->rendererCount);
172
173 var crossFadeLODBegin = 0;
174
175 if (useSpeedTreeCrossFade)
176 {
177 var lastLODIndex = lodOffset + (lodCount - 1);
178 var hasBillboardLOD = lodCount > 0 && inputData.lodRenderersCount[lastLODIndex] == 1 && lastLODIsBillboard;
179
180 if (lodCount == 0)
181 crossFadeLODBegin = 0;
182 else if (hasBillboardLOD)
183 crossFadeLODBegin = Math.Max(lodCount, 2) - 2;
184 else
185 crossFadeLODBegin = lodCount - 1;
186 }
187
188 for (int i = 0; i < lodCount; ++i)
189 {
190 var lodIndex = lodOffset + i;
191 var lodHeight = inputData.lodScreenRelativeTransitionHeight[lodIndex];
192 var lodDist = LODGroupRenderingUtils.CalculateLODDistance(lodHeight, worldSpaceSize);
193
194 lodGroupData->screenRelativeTransitionHeights[i] = lodHeight;
195 lodGroupData->fadeTransitionWidth[i] = 0.0f;
196 lodGroupCullingData->sqrDistances[i] = lodDist * lodDist;
197 lodGroupCullingData->percentageFlags[i] = false;
198 lodGroupCullingData->transitionDistances[i] = 0.0f;
199
200 if (useSpeedTreeCrossFade && i < crossFadeLODBegin)
201 {
202 lodGroupCullingData->percentageFlags[i] = true;
203 }
204 else if (useDitheringCrossFade && i >= crossFadeLODBegin)
205 {
206 var fadeTransitionWidth = inputData.lodFadeTransitionWidth[lodIndex];
207 var prevLODHeight = i != 0 ? inputData.lodScreenRelativeTransitionHeight[lodIndex - 1] : 1.0f;
208 var transitionHeight = lodHeight + fadeTransitionWidth * (prevLODHeight - lodHeight);
209 var transitionDistance = lodDist - LODGroupRenderingUtils.CalculateLODDistance(transitionHeight, worldSpaceSize);
210 transitionDistance = Mathf.Max(0.0f, transitionDistance);
211
212 lodGroupData->fadeTransitionWidth[i] = fadeTransitionWidth;
213 lodGroupCullingData->transitionDistances[i] = transitionDistance;
214 }
215 }
216 }
217 }
218
219 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
220 internal unsafe struct FreeLODGroupDataJob : IJob
221 {
222 [ReadOnly] public NativeArray<int> destroyedLODGroupsID;
223
224 public NativeList<LODGroupData> lodGroupsData;
225 public NativeParallelHashMap<int, GPUInstanceIndex> lodGroupDataHash;
226 public NativeList<GPUInstanceIndex> freeLODGroupDataHandles;
227
228 [NativeDisableUnsafePtrRestriction] public int* removedRendererCount;
229
230 public void Execute()
231 {
232 foreach (int lodGroupID in destroyedLODGroupsID)
233 {
234 if (lodGroupDataHash.TryGetValue(lodGroupID, out var lodGroupInstance))
235 {
236 Assert.IsTrue(lodGroupInstance.valid);
237
238 lodGroupDataHash.Remove(lodGroupID);
239 freeLODGroupDataHandles.Add(lodGroupInstance);
240
241 ref LODGroupData lodGroupData = ref lodGroupsData.ElementAt(lodGroupInstance.index);
242 Assert.IsTrue(lodGroupData.valid);
243
244 *removedRendererCount += lodGroupData.rendererCount;
245 lodGroupData.valid = false;
246 }
247 }
248 }
249 }
250
251 internal class LODGroupDataPool : IDisposable
252 {
253 private NativeList<LODGroupData> m_LODGroupData;
254 private NativeParallelHashMap<int, GPUInstanceIndex> m_LODGroupDataHash;
255 public NativeParallelHashMap<int, GPUInstanceIndex> lodGroupDataHash => m_LODGroupDataHash;
256
257 private NativeList<LODGroupCullingData> m_LODGroupCullingData;
258 private NativeList<GPUInstanceIndex> m_FreeLODGroupDataHandles;
259
260 private int m_CrossfadedRendererCount;
261 private bool m_SupportDitheringCrossFade;
262
263 public NativeList<LODGroupCullingData> lodGroupCullingData => m_LODGroupCullingData;
264 public int crossfadedRendererCount => m_CrossfadedRendererCount;
265
266 public int activeLodGroupCount => m_LODGroupData.Length;
267
268 private static class LodGroupShaderIDs
269 {
270 public static readonly int _SupportDitheringCrossFade = Shader.PropertyToID("_SupportDitheringCrossFade");
271 public static readonly int _LodGroupCullingDataGPUByteSize = Shader.PropertyToID("_LodGroupCullingDataGPUByteSize");
272 public static readonly int _LodGroupCullingDataStartOffset = Shader.PropertyToID("_LodGroupCullingDataStartOffset");
273 public static readonly int _LodCullingDataQueueCount = Shader.PropertyToID("_LodCullingDataQueueCount");
274 public static readonly int _InputLodCullingDataIndices = Shader.PropertyToID("_InputLodCullingDataIndices");
275 public static readonly int _InputLodCullingDataBuffer = Shader.PropertyToID("_InputLodCullingDataBuffer");
276 public static readonly int _LodGroupCullingData = Shader.PropertyToID("_LodGroupCullingData");
277 }
278
279 public LODGroupDataPool(GPUResidentDrawerResources resources, int initialInstanceCount, bool supportDitheringCrossFade)
280 {
281 m_LODGroupData = new NativeList<LODGroupData>(Allocator.Persistent);
282 m_LODGroupDataHash = new NativeParallelHashMap<int, GPUInstanceIndex>(64, Allocator.Persistent);
283
284 m_LODGroupCullingData = new NativeList<LODGroupCullingData>(Allocator.Persistent);
285 m_FreeLODGroupDataHandles = new NativeList<GPUInstanceIndex>(Allocator.Persistent);
286
287 m_SupportDitheringCrossFade = supportDitheringCrossFade;
288 }
289
290 public void Dispose()
291 {
292 m_LODGroupData.Dispose();
293 m_LODGroupDataHash.Dispose();
294
295 m_LODGroupCullingData.Dispose();
296 m_FreeLODGroupDataHandles.Dispose();
297 }
298
299 public unsafe void UpdateLODGroupTransformData(in GPUDrivenLODGroupData inputData)
300 {
301 var lodGroupCount = inputData.lodGroupID.Length;
302
303 var updateCount = 0;
304
305 var jobData = new UpdateLODGroupTransformJob()
306 {
307 lodGroupDataHash = m_LODGroupDataHash,
308 lodGroupIDs = inputData.lodGroupID,
309 worldSpaceReferencePoints = inputData.worldSpaceReferencePoint,
310 worldSpaceSizes = inputData.worldSpaceSize,
311 lodGroupData = m_LODGroupData,
312 lodGroupCullingData = m_LODGroupCullingData,
313 supportDitheringCrossFade = m_SupportDitheringCrossFade,
314 atomicUpdateCount = new UnsafeAtomicCounter32(&updateCount),
315 };
316
317 if (lodGroupCount >= UpdateLODGroupTransformJob.k_BatchSize)
318 jobData.Schedule(lodGroupCount, UpdateLODGroupTransformJob.k_BatchSize).Complete();
319 else
320 jobData.Run(lodGroupCount);
321 }
322
323 public unsafe void UpdateLODGroupData(in GPUDrivenLODGroupData inputData)
324 {
325 FreeLODGroupData(inputData.invalidLODGroupID);
326
327 var lodGroupInstances = new NativeArray<GPUInstanceIndex>(inputData.lodGroupID.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
328
329 int previousRendererCount = 0;
330
331 new AllocateOrGetLODGroupDataInstancesJob
332 {
333 lodGroupsID = inputData.lodGroupID,
334 lodGroupsData = m_LODGroupData,
335 lodGroupCullingData = m_LODGroupCullingData,
336 lodGroupDataHash = m_LODGroupDataHash,
337 freeLODGroupDataHandles = m_FreeLODGroupDataHandles,
338 lodGroupInstances = lodGroupInstances,
339 previousRendererCount = &previousRendererCount
340 }.Run();
341
342 m_CrossfadedRendererCount -= previousRendererCount;
343 Assert.IsTrue(m_CrossfadedRendererCount >= 0);
344
345 int rendererCount = 0;
346
347 var updateLODGroupDataJobData = new UpdateLODGroupDataJob
348 {
349 lodGroupInstances = lodGroupInstances,
350 inputData = inputData,
351 supportDitheringCrossFade = m_SupportDitheringCrossFade,
352 lodGroupsData = m_LODGroupData.AsArray(),
353 lodGroupsCullingData = m_LODGroupCullingData.AsArray(),
354 rendererCount = new UnsafeAtomicCounter32(&rendererCount),
355 };
356
357 if (lodGroupInstances.Length >= UpdateLODGroupTransformJob.k_BatchSize)
358 updateLODGroupDataJobData.Schedule(lodGroupInstances.Length, UpdateLODGroupTransformJob.k_BatchSize).Complete();
359 else
360 updateLODGroupDataJobData.Run(lodGroupInstances.Length);
361
362 m_CrossfadedRendererCount += rendererCount;
363
364 lodGroupInstances.Dispose();
365 }
366
367 public unsafe void FreeLODGroupData(NativeArray<int> destroyedLODGroupsID)
368 {
369 if (destroyedLODGroupsID.Length == 0)
370 return;
371
372 int removedRendererCount = 0;
373
374 new FreeLODGroupDataJob
375 {
376 destroyedLODGroupsID = destroyedLODGroupsID,
377 lodGroupsData = m_LODGroupData,
378 lodGroupDataHash = m_LODGroupDataHash,
379 freeLODGroupDataHandles = m_FreeLODGroupDataHandles,
380 removedRendererCount = &removedRendererCount
381 }.Run();
382
383 m_CrossfadedRendererCount -= removedRendererCount;
384 Assert.IsTrue(m_CrossfadedRendererCount >= 0);
385 }
386 }
387}