A game about forced loneliness, made by TACStudios
1using System;
2using System.Threading;
3using UnityEngine.Assertions;
4using Unity.Collections;
5using Unity.Jobs;
6using Unity.Jobs.LowLevel.Unsafe;
7using Unity.Collections.LowLevel.Unsafe;
8using Unity.Burst;
9using UnityEngine.Profiling;
10
11[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.RegisterNewInstancesJob<UnityEngine.Rendering.BatchMeshID>))]
12[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.RegisterNewInstancesJob<UnityEngine.Rendering.BatchMaterialID>))]
13[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.FindNonRegisteredInstancesJob<UnityEngine.Rendering.BatchMeshID>))]
14[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.FindNonRegisteredInstancesJob<UnityEngine.Rendering.BatchMaterialID>))]
15
16namespace UnityEngine.Rendering
17{
18 internal delegate void OnCullingCompleteCallback(JobHandle jobHandle, in BatchCullingContext cullingContext, in BatchCullingOutput cullingOutput);
19
20 internal struct InstanceCullingBatcherDesc
21 {
22 public OnCullingCompleteCallback onCompleteCallback;
23
24#if UNITY_EDITOR
25 public Shader brgPicking;
26 public Shader brgLoading;
27 public Shader brgError;
28#endif
29
30 public static InstanceCullingBatcherDesc NewDefault()
31 {
32 return new InstanceCullingBatcherDesc()
33 {
34 onCompleteCallback = null
35#if UNITY_EDITOR
36 ,brgPicking = null
37 ,brgLoading = null
38 ,brgError = null
39#endif
40 };
41 }
42 }
43
44 internal struct MeshProceduralInfo
45 {
46 public MeshTopology topology;
47 public uint baseVertex;
48 public uint firstIndex;
49 public uint indexCount;
50 }
51
52 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
53 internal struct PrefixSumDrawInstancesJob : IJob
54 {
55 [ReadOnly] public NativeParallelHashMap<RangeKey, int> rangeHash;
56
57 public NativeList<DrawRange> drawRanges;
58 public NativeList<DrawBatch> drawBatches;
59 public NativeArray<int> drawBatchIndices;
60
61 public void Execute()
62 {
63 Assert.AreEqual(rangeHash.Count(), drawRanges.Length);
64 Assert.AreEqual(drawBatchIndices.Length, drawBatches.Length);
65
66 // Prefix sum to calculate draw offsets for each DrawRange
67 int drawPrefixSum = 0;
68
69 for (int i = 0; i < drawRanges.Length; ++i)
70 {
71 ref DrawRange drawRange = ref drawRanges.ElementAt(i);
72 drawRange.drawOffset = drawPrefixSum;
73 drawPrefixSum += drawRange.drawCount;
74 }
75
76 // Generate DrawBatch index ranges for each DrawRange
77 var internalRangeIndex = new NativeArray<int>(drawRanges.Length, Allocator.Temp);
78
79 for (int i = 0; i < drawBatches.Length; ++i)
80 {
81 ref DrawBatch drawBatch = ref drawBatches.ElementAt(i);
82 Assert.IsTrue(drawBatch.instanceCount > 0);
83
84 if (rangeHash.TryGetValue(drawBatch.key.range, out int drawRangeIndex))
85 {
86 ref DrawRange drawRange = ref drawRanges.ElementAt(drawRangeIndex);
87 drawBatchIndices[drawRange.drawOffset + internalRangeIndex[drawRangeIndex]] = i;
88 internalRangeIndex[drawRangeIndex]++;
89 }
90 }
91
92 // Prefix sum to calculate instance offsets for each DrawCommand
93 int drawInstancesPrefixSum = 0;
94
95 for (int i = 0; i < drawBatchIndices.Length; ++i)
96 {
97 // DrawIndices remap to get DrawCommands ordered by DrawRange
98 var drawBatchIndex = drawBatchIndices[i];
99 ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
100 drawBatch.instanceOffset = drawInstancesPrefixSum;
101 drawInstancesPrefixSum += drawBatch.instanceCount;
102 }
103
104 internalRangeIndex.Dispose();
105 }
106 }
107
108 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
109 internal unsafe struct BuildDrawListsJob : IJobParallelFor
110 {
111 public const int k_BatchSize = 128;
112 public const int k_IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int);
113
114 [ReadOnly] public NativeParallelHashMap<DrawKey, int> batchHash;
115 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
116 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawBatch> drawBatches;
117
118 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> internalDrawIndex;
119 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> drawInstanceIndices;
120
121 private unsafe static int IncrementCounter(int* counter)
122 {
123 return Interlocked.Increment(ref UnsafeUtility.AsRef<int>(counter)) - 1;
124 }
125
126 public void Execute(int index)
127 {
128 // Generate instance index ranges for each DrawCommand
129 ref DrawInstance drawInstance = ref drawInstances.ElementAt(index);
130 int drawBatchIndex = batchHash[drawInstance.key];
131
132 ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
133 var offset = IncrementCounter((int*)internalDrawIndex.GetUnsafePtr() + drawBatchIndex * k_IntsPerCacheLine);
134 var writeIndex = drawBatch.instanceOffset + offset;
135 drawInstanceIndices[writeIndex] = drawInstance.instanceIndex;
136 }
137 }
138
139 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
140 internal unsafe struct FindDrawInstancesJob : IJobParallelForBatch
141 {
142 public const int k_BatchSize = 128;
143
144 [ReadOnly] public NativeArray<InstanceHandle> instancesSorted;
145 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
146
147 [WriteOnly] public NativeList<int>.ParallelWriter outDrawInstanceIndicesWriter;
148
149 public void Execute(int startIndex, int count)
150 {
151 int* instancesToRemove = stackalloc int[k_BatchSize];
152 int length = 0;
153
154 for (int i = startIndex; i < startIndex + count; ++i)
155 {
156 ref DrawInstance drawInstance = ref drawInstances.ElementAt(i);
157
158 if (instancesSorted.BinarySearch(InstanceHandle.FromInt(drawInstance.instanceIndex)) >= 0)
159 instancesToRemove[length++] = i;
160 }
161
162 outDrawInstanceIndicesWriter.AddRangeNoResize(instancesToRemove, length);
163 }
164 }
165
166 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
167 internal unsafe struct FindMaterialDrawInstancesJob : IJobParallelForBatch
168 {
169 public const int k_BatchSize = 128;
170
171 [ReadOnly] public NativeArray<uint> materialsSorted;
172 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
173
174 [WriteOnly] public NativeList<int>.ParallelWriter outDrawInstanceIndicesWriter;
175
176 public void Execute(int startIndex, int count)
177 {
178 int* instancesToRemove = stackalloc int[k_BatchSize];
179 int length = 0;
180
181 for (int i = startIndex; i < startIndex + count; ++i)
182 {
183 ref DrawInstance drawInstance = ref drawInstances.ElementAt(i);
184
185 if (materialsSorted.BinarySearch(drawInstance.key.materialID.value) >= 0)
186 instancesToRemove[length++] = i;
187 }
188
189 outDrawInstanceIndicesWriter.AddRangeNoResize(instancesToRemove, length);
190 }
191 }
192
193 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
194 internal struct FindNonRegisteredInstancesJob<T> : IJobParallelForBatch where T : unmanaged
195 {
196 public const int k_BatchSize = 128;
197
198 [ReadOnly] public NativeArray<int> instanceIDs;
199 [ReadOnly] public NativeParallelHashMap<int, T> hashMap;
200
201 [WriteOnly] public NativeList<int>.ParallelWriter outInstancesWriter;
202
203 public unsafe void Execute(int startIndex, int count)
204 {
205 int* notFoundinstanceIDs = stackalloc int[k_BatchSize];
206 int length = 0;
207
208 for (int i = startIndex; i < startIndex + count; ++i)
209 {
210 int instanceID = instanceIDs[i];
211
212 if (!hashMap.ContainsKey(instanceID))
213 notFoundinstanceIDs[length++] = instanceID;
214 }
215
216 outInstancesWriter.AddRangeNoResize(notFoundinstanceIDs, length);
217 }
218 }
219
220 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
221 internal struct RegisterNewInstancesJob<T> : IJobParallelFor where T : unmanaged
222 {
223 public const int k_BatchSize = 128;
224
225 [ReadOnly] public NativeArray<int> instanceIDs;
226 [ReadOnly] public NativeArray<T> batchIDs;
227
228 [WriteOnly] public NativeParallelHashMap<int, T>.ParallelWriter hashMap;
229
230 public unsafe void Execute(int index)
231 {
232 hashMap.TryAdd(instanceIDs[index], batchIDs[index]);
233 }
234 }
235
236 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
237 internal struct RemoveDrawInstanceIndicesJob : IJob
238 {
239 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeArray<int> drawInstanceIndices;
240
241 public NativeList<DrawInstance> drawInstances;
242 public NativeParallelHashMap<RangeKey, int> rangeHash;
243 public NativeParallelHashMap<DrawKey, int> batchHash;
244 public NativeList<DrawRange> drawRanges;
245 public NativeList<DrawBatch> drawBatches;
246
247 public void RemoveDrawRange(in RangeKey key)
248 {
249 int drawRangeIndex = rangeHash[key];
250
251 ref DrawRange lastDrawRange = ref drawRanges.ElementAt(drawRanges.Length - 1);
252 rangeHash[lastDrawRange.key] = drawRangeIndex;
253
254 rangeHash.Remove(key);
255 drawRanges.RemoveAtSwapBack(drawRangeIndex);
256 }
257
258 public void RemoveDrawBatch(in DrawKey key)
259 {
260 int drawBatchIndex = batchHash[key];
261
262 ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
263
264 int drawRangeIndex = rangeHash[key.range];
265 ref DrawRange drawRange = ref drawRanges.ElementAt(drawRangeIndex);
266
267 Assert.IsTrue(drawRange.drawCount > 0);
268
269 if (--drawRange.drawCount == 0)
270 RemoveDrawRange(drawRange.key);
271
272 ref DrawBatch lastDrawBatch = ref drawBatches.ElementAt(drawBatches.Length - 1);
273 batchHash[lastDrawBatch.key] = drawBatchIndex;
274
275 batchHash.Remove(key);
276 drawBatches.RemoveAtSwapBack(drawBatchIndex);
277 }
278
279 public unsafe void Execute()
280 {
281 var drawInstancesPtr = (DrawInstance*)drawInstances.GetUnsafePtr();
282 var drawInstancesNewBack = drawInstances.Length - 1;
283
284 for (int indexRev = drawInstanceIndices.Length - 1; indexRev >= 0; --indexRev)
285 {
286 int indexToRemove = drawInstanceIndices[indexRev];
287 DrawInstance* drawInstance = drawInstancesPtr + indexToRemove;
288
289 int drawBatchIndex = batchHash[drawInstance->key];
290 ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
291
292 Assert.IsTrue(drawBatch.instanceCount > 0);
293
294 if (--drawBatch.instanceCount == 0)
295 RemoveDrawBatch(drawBatch.key);
296
297 UnsafeUtility.MemCpy(drawInstance, drawInstancesPtr + drawInstancesNewBack--, sizeof(DrawInstance));
298 }
299
300 drawInstances.ResizeUninitialized(drawInstancesNewBack + 1);
301 }
302 }
303
304 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
305 internal struct CreateDrawBatchesJob : IJob
306 {
307 [ReadOnly] public bool implicitInstanceIndices;
308 [ReadOnly] public NativeArray<InstanceHandle> instances;
309 [ReadOnly] public GPUDrivenRendererGroupData rendererData;
310 [ReadOnly] public NativeParallelHashMap<int, BatchMeshID> batchMeshHash;
311 [ReadOnly] public NativeParallelHashMap<int, BatchMaterialID> batchMaterialHash;
312
313 public NativeParallelHashMap<RangeKey, int> rangeHash;
314 public NativeList<DrawRange> drawRanges;
315 public NativeParallelHashMap<DrawKey, int> batchHash;
316 public NativeList<DrawBatch> drawBatches;
317
318 [WriteOnly] public NativeList<DrawInstance> drawInstances;
319
320 private ref DrawRange EditDrawRange(in RangeKey key)
321 {
322 int drawRangeIndex;
323
324 if (!rangeHash.TryGetValue(key, out drawRangeIndex))
325 {
326 var drawRange = new DrawRange { key = key, drawCount = 0, drawOffset = 0 };
327 drawRangeIndex = drawRanges.Length;
328 rangeHash.Add(key, drawRangeIndex);
329 drawRanges.Add(drawRange);
330 }
331
332 ref DrawRange data = ref drawRanges.ElementAt(drawRangeIndex);
333 Assert.IsTrue(data.key.Equals(key));
334
335 return ref data;
336 }
337
338 private ref DrawBatch EditDrawBatch(in DrawKey key, in SubMeshDescriptor subMeshDescriptor)
339 {
340 var procInfo = new MeshProceduralInfo();
341 procInfo.topology = subMeshDescriptor.topology;
342 procInfo.baseVertex = (uint)subMeshDescriptor.baseVertex;
343 procInfo.firstIndex = (uint)subMeshDescriptor.indexStart;
344 procInfo.indexCount = (uint)subMeshDescriptor.indexCount;
345
346 int drawBatchIndex;
347
348 if (!batchHash.TryGetValue(key, out drawBatchIndex))
349 {
350 var drawBatch = new DrawBatch() { key = key, instanceCount = 0, instanceOffset = 0, procInfo = procInfo };
351 drawBatchIndex = drawBatches.Length;
352 batchHash.Add(key, drawBatchIndex);
353 drawBatches.Add(drawBatch);
354 }
355
356 ref DrawBatch data = ref drawBatches.ElementAt(drawBatchIndex);
357 Assert.IsTrue(data.key.Equals(key));
358
359 return ref data;
360 }
361
362 public void ProcessRenderer(int i)
363 {
364 var meshIndex = rendererData.meshIndex[i];
365 var meshID = rendererData.meshID[meshIndex];
366 var submeshCount = rendererData.subMeshCount[meshIndex];
367 var subMeshDescOffset = rendererData.subMeshDescOffset[meshIndex];
368 var batchMeshID = batchMeshHash[meshID];
369 var rendererGroupID = rendererData.rendererGroupID[i];
370 var startSubMesh = rendererData.subMeshStartIndex[i];
371 var gameObjectLayer = rendererData.gameObjectLayer[i];
372 var renderingLayerMask = rendererData.renderingLayerMask[i];
373 var materialsOffset = rendererData.materialsOffset[i];
374 var materialsCount = rendererData.materialsCount[i];
375 var lightmapIndex = rendererData.lightmapIndex[i];
376 var packedRendererData = rendererData.packedRendererData[i];
377 var rendererPriority = rendererData.rendererPriority[i];
378 var lodGroupID = rendererData.lodGroupID[i];
379
380 int instanceCount;
381 int instanceOffset;
382
383 if (implicitInstanceIndices)
384 {
385 instanceCount = 1;
386 instanceOffset = i;
387 }
388 else
389 {
390 instanceCount = rendererData.instancesCount[i];
391 instanceOffset = rendererData.instancesOffset[i];
392 }
393
394 if (instanceCount == 0)
395 return;
396
397 const int kLightmapIndexMask = 0xffff;
398 const int kLightmapIndexInfluenceOnly = 0xfffe;
399
400 var overridenComponents = InstanceComponentGroup.Default;
401
402 // Add per-instance wind parameters
403 if(packedRendererData.hasTree)
404 overridenComponents |= InstanceComponentGroup.Wind;
405
406 var lmIndexMasked = lightmapIndex & kLightmapIndexMask;
407
408 // Object doesn't have a valid lightmap Index, -> uses probes for lighting
409 if (lmIndexMasked >= kLightmapIndexInfluenceOnly)
410 {
411 // Only add the component when needed to store blended results (shader will use the ambient probe when not present)
412 if (packedRendererData.lightProbeUsage == LightProbeUsage.BlendProbes)
413 overridenComponents |= InstanceComponentGroup.LightProbe;
414 }
415 else
416 {
417 // Add per-instance lightmap parameters
418 overridenComponents |= InstanceComponentGroup.Lightmap;
419 }
420
421 // Scan all materials once to retrieve whether this renderer is indirect-compatible or not (and store it in the RangeKey).
422 var supportsIndirect = true;
423 for (int matIndex = 0; matIndex < materialsCount; ++matIndex)
424 {
425 if (matIndex >= submeshCount)
426 {
427 Debug.LogWarning("Material count in the shared material list is higher than sub mesh count for the mesh. Object may be corrupted.");
428 continue;
429 }
430
431 var materialIndex = rendererData.materialIndex[materialsOffset + matIndex];
432 var packedMaterialData = rendererData.packedMaterialData[materialIndex];
433 supportsIndirect &= packedMaterialData.isIndirectSupported;
434 }
435
436 var rangeKey = new RangeKey
437 {
438 layer = (byte)gameObjectLayer,
439 renderingLayerMask = renderingLayerMask,
440 motionMode = packedRendererData.motionVecGenMode,
441 shadowCastingMode = packedRendererData.shadowCastingMode,
442 staticShadowCaster = packedRendererData.staticShadowCaster,
443 rendererPriority = rendererPriority,
444 supportsIndirect = supportsIndirect
445 };
446
447 ref DrawRange drawRange = ref EditDrawRange(rangeKey);
448
449 for (int matIndex = 0; matIndex < materialsCount; ++matIndex)
450 {
451 if (matIndex >= submeshCount)
452 {
453 Debug.LogWarning("Material count in the shared material list is higher than sub mesh count for the mesh. Object may be corrupted.");
454 continue;
455 }
456
457 var materialIndex = rendererData.materialIndex[materialsOffset + matIndex];
458 var materialID = rendererData.materialID[materialIndex];
459 var packedMaterialData = rendererData.packedMaterialData[materialIndex];
460
461 if (materialID == 0)
462 {
463 Debug.LogWarning("Material in the shared materials list is null. Object will be partially rendered.");
464 continue;
465 }
466
467 batchMaterialHash.TryGetValue(materialID, out BatchMaterialID batchMaterialID);
468
469 // We always provide crossfade value packed in instance index. We don't use None even if there is no LOD to not split the batch.
470 var flags = BatchDrawCommandFlags.LODCrossFadeValuePacked;
471
472 // Let the engine know if we've opted out of lightmap texture arrays
473 flags |= BatchDrawCommandFlags.UseLegacyLightmapsKeyword;
474
475 // assume that a custom motion vectors pass contains deformation motion, so should always output motion vectors
476 // (otherwise this flag is set dynamically during culling only when the transform is changing)
477 if (packedMaterialData.isMotionVectorsPassEnabled)
478 flags |= BatchDrawCommandFlags.HasMotion;
479
480 if (packedMaterialData.isTransparent)
481 flags |= BatchDrawCommandFlags.HasSortingPosition;
482
483 {
484 var submeshIndex = startSubMesh + matIndex;
485 var subMeshDesc = rendererData.subMeshDesc[subMeshDescOffset + submeshIndex];
486
487 var drawKey = new DrawKey
488 {
489 materialID = batchMaterialID,
490 meshID = batchMeshID,
491 submeshIndex = submeshIndex,
492 flags = flags,
493 transparentInstanceId = packedMaterialData.isTransparent ? rendererGroupID : 0,
494 range = rangeKey,
495 overridenComponents = (uint)overridenComponents,
496 // When we've opted out of lightmap texture arrays, we
497 // need to pass in a valid lightmap index. The engine
498 // uses this index for sorting and for breaking the
499 // batch when lightmaps change across draw calls, and
500 // for binding the correct light map.
501 lightmapIndex = lightmapIndex
502 };
503
504 ref DrawBatch drawBatch = ref EditDrawBatch(drawKey, subMeshDesc);
505
506 if (drawBatch.instanceCount == 0)
507 ++drawRange.drawCount;
508
509 drawBatch.instanceCount += instanceCount;
510
511 for (int j = 0; j < instanceCount; ++j)
512 {
513 var instanceIndex = instanceOffset + j;
514 InstanceHandle instance = instances[instanceIndex];
515 drawInstances.Add(new DrawInstance { key = drawKey, instanceIndex = instance.index });
516 }
517 }
518 }
519 }
520
521 public void Execute()
522 {
523 {
524 for (int i = 0; i < rendererData.rendererGroupID.Length; ++i)
525 ProcessRenderer(i);
526 }
527 }
528 }
529
530 internal class CPUDrawInstanceData
531 {
532 public NativeList<DrawInstance> drawInstances => m_DrawInstances;
533 public NativeParallelHashMap<DrawKey, int> batchHash => m_BatchHash;
534 public NativeList<DrawBatch> drawBatches => m_DrawBatches;
535 public NativeParallelHashMap<RangeKey, int> rangeHash => m_RangeHash;
536 public NativeList<DrawRange> drawRanges => m_DrawRanges;
537 public NativeArray<int> drawBatchIndices => m_DrawBatchIndices.AsArray();
538 public NativeArray<int> drawInstanceIndices => m_DrawInstanceIndices.AsArray();
539
540 private NativeParallelHashMap<RangeKey, int> m_RangeHash; // index in m_DrawRanges, hashes by range state
541 private NativeList<DrawRange> m_DrawRanges;
542 private NativeParallelHashMap<DrawKey, int> m_BatchHash; // index in m_DrawBatches, hashed by draw state
543 private NativeList<DrawBatch> m_DrawBatches;
544 private NativeList<DrawInstance> m_DrawInstances;
545 private NativeList<int> m_DrawInstanceIndices; // DOTS instance index, arranged in contiguous blocks in m_DrawBatches order (see DrawBatch.instanceOffset, DrawBatch.instanceCount)
546 private NativeList<int> m_DrawBatchIndices; // index in m_DrawBatches, arranged in contiguous blocks in m_DrawRanges order (see DrawRange.drawOffset, DrawRange.drawCount)
547
548 private bool m_NeedsRebuild;
549
550 public bool valid => m_DrawInstances.IsCreated;
551
552 public void Initialize()
553 {
554 Assert.IsTrue(!valid);
555 m_RangeHash = new NativeParallelHashMap<RangeKey, int>(1024, Allocator.Persistent);
556 m_DrawRanges = new NativeList<DrawRange>(Allocator.Persistent);
557 m_BatchHash = new NativeParallelHashMap<DrawKey, int>(1024, Allocator.Persistent);
558 m_DrawBatches = new NativeList<DrawBatch>(Allocator.Persistent);
559 m_DrawInstances = new NativeList<DrawInstance>(1024, Allocator.Persistent);
560 m_DrawInstanceIndices = new NativeList<int>(1024, Allocator.Persistent);
561 m_DrawBatchIndices = new NativeList<int>(1024, Allocator.Persistent);
562 }
563
564 public void Dispose()
565 {
566 if (m_DrawBatchIndices.IsCreated)
567 m_DrawBatchIndices.Dispose();
568
569 if (m_DrawInstanceIndices.IsCreated)
570 m_DrawInstanceIndices.Dispose();
571
572 if (m_DrawInstances.IsCreated)
573 m_DrawInstances.Dispose();
574
575 if (m_DrawBatches.IsCreated)
576 m_DrawBatches.Dispose();
577
578 if (m_BatchHash.IsCreated)
579 m_BatchHash.Dispose();
580
581 if (m_DrawRanges.IsCreated)
582 m_DrawRanges.Dispose();
583
584 if (m_RangeHash.IsCreated)
585 m_RangeHash.Dispose();
586 }
587
588 public void RebuildDrawListsIfNeeded()
589 {
590 if (!m_NeedsRebuild)
591 return;
592
593 m_NeedsRebuild = false;
594
595 Assert.IsTrue(m_RangeHash.Count() == m_DrawRanges.Length);
596 Assert.IsTrue(m_BatchHash.Count() == m_DrawBatches.Length);
597
598 m_DrawInstanceIndices.ResizeUninitialized(m_DrawInstances.Length);
599 m_DrawBatchIndices.ResizeUninitialized(m_DrawBatches.Length);
600
601 var internalDrawIndex = new NativeArray<int>(drawBatches.Length * BuildDrawListsJob.k_IntsPerCacheLine, Allocator.TempJob, NativeArrayOptions.ClearMemory);
602
603 var prefixSumDrawInstancesJob = new PrefixSumDrawInstancesJob()
604 {
605 rangeHash = m_RangeHash,
606 drawRanges = m_DrawRanges,
607 drawBatches = m_DrawBatches,
608 drawBatchIndices = m_DrawBatchIndices.AsArray()
609 };
610
611 var prefixSumJobHandle = prefixSumDrawInstancesJob.Schedule();
612
613 var buildDrawListsJob = new BuildDrawListsJob()
614 {
615 drawInstances = m_DrawInstances,
616 batchHash = m_BatchHash,
617 drawBatches = m_DrawBatches,
618 internalDrawIndex = internalDrawIndex,
619 drawInstanceIndices = m_DrawInstanceIndices.AsArray(),
620 };
621
622 buildDrawListsJob.Schedule(m_DrawInstances.Length, BuildDrawListsJob.k_BatchSize, prefixSumJobHandle).Complete();
623
624 internalDrawIndex.Dispose();
625 }
626
627 public unsafe void DestroyDrawInstanceIndices(NativeArray<int> drawInstanceIndicesToDestroy)
628 {
629 Profiler.BeginSample("DestroyDrawInstanceIndices.ParallelSort");
630 drawInstanceIndicesToDestroy.ParallelSort().Complete();
631 Profiler.EndSample();
632
633 var removeDrawInstanceIndicesJob = new RemoveDrawInstanceIndicesJob
634 {
635 drawInstanceIndices = drawInstanceIndicesToDestroy,
636 drawInstances = m_DrawInstances,
637 drawBatches = m_DrawBatches,
638 drawRanges = m_DrawRanges,
639 batchHash = m_BatchHash,
640 rangeHash = m_RangeHash
641 };
642
643 removeDrawInstanceIndicesJob.Run();
644 }
645
646 public unsafe void DestroyDrawInstances(NativeArray<InstanceHandle> destroyedInstances)
647 {
648 if (m_DrawInstances.IsEmpty || destroyedInstances.Length == 0)
649 return;
650
651 NeedsRebuild();
652
653 var destroyedInstancesSorted = new NativeArray<InstanceHandle>(destroyedInstances, Allocator.TempJob);
654 Assert.AreEqual(UnsafeUtility.SizeOf<InstanceHandle>(), UnsafeUtility.SizeOf<int>());
655
656 Profiler.BeginSample("DestroyDrawInstances.ParallelSort");
657 destroyedInstancesSorted.Reinterpret<int>().ParallelSort().Complete();
658 Profiler.EndSample();
659
660 var drawInstanceIndicesToDestroy = new NativeList<int>(m_DrawInstances.Length, Allocator.TempJob);
661
662 var findDrawInstancesJobHandle = new FindDrawInstancesJob()
663 {
664 instancesSorted = destroyedInstancesSorted,
665 drawInstances = m_DrawInstances,
666 outDrawInstanceIndicesWriter = drawInstanceIndicesToDestroy.AsParallelWriter()
667 };
668
669 findDrawInstancesJobHandle.ScheduleBatch(m_DrawInstances.Length, FindDrawInstancesJob.k_BatchSize).Complete();
670
671 DestroyDrawInstanceIndices(drawInstanceIndicesToDestroy.AsArray());
672
673 destroyedInstancesSorted.Dispose();
674 drawInstanceIndicesToDestroy.Dispose();
675 }
676
677 public unsafe void DestroyMaterialDrawInstances(NativeArray<uint> destroyedBatchMaterials)
678 {
679 if (m_DrawInstances.IsEmpty || destroyedBatchMaterials.Length == 0)
680 return;
681
682 NeedsRebuild();
683
684 var destroyedBatchMaterialsSorted = new NativeArray<uint>(destroyedBatchMaterials, Allocator.TempJob);
685
686 Profiler.BeginSample("DestroyedBatchMaterials.ParallelSort");
687 destroyedBatchMaterialsSorted.Reinterpret<int>().ParallelSort().Complete();
688 Profiler.EndSample();
689
690 var drawInstanceIndicesToDestroy = new NativeList<int>(m_DrawInstances.Length, Allocator.TempJob);
691
692 var findDrawInstancesJobHandle = new FindMaterialDrawInstancesJob()
693 {
694 materialsSorted = destroyedBatchMaterialsSorted,
695 drawInstances = m_DrawInstances,
696 outDrawInstanceIndicesWriter = drawInstanceIndicesToDestroy.AsParallelWriter()
697 };
698
699 findDrawInstancesJobHandle.ScheduleBatch(m_DrawInstances.Length, FindMaterialDrawInstancesJob.k_BatchSize).Complete();
700
701 DestroyDrawInstanceIndices(drawInstanceIndicesToDestroy.AsArray());
702
703 destroyedBatchMaterialsSorted.Dispose();
704 drawInstanceIndicesToDestroy.Dispose();
705 }
706
707 public void NeedsRebuild()
708 {
709 m_NeedsRebuild = true;
710 }
711 }
712
713 internal class InstanceCullingBatcher : IDisposable
714 {
715 private RenderersBatchersContext m_BatchersContext;
716 private CPUDrawInstanceData m_DrawInstanceData;
717 private BatchRendererGroup m_BRG;
718 private NativeParallelHashMap<uint, BatchID> m_GlobalBatchIDs;
719 private InstanceCuller m_Culler;
720 private NativeParallelHashMap<int, BatchMaterialID> m_BatchMaterialHash;
721 private NativeParallelHashMap<int, BatchMeshID> m_BatchMeshHash;
722
723 private int m_CachedInstanceDataBufferLayoutVersion;
724
725 private OnCullingCompleteCallback m_OnCompleteCallback;
726
727 public NativeParallelHashMap<int, BatchMaterialID> batchMaterialHash => m_BatchMaterialHash;
728
729 public InstanceCullingBatcher(RenderersBatchersContext batcherContext, InstanceCullingBatcherDesc desc, BatchRendererGroup.OnFinishedCulling onFinishedCulling)
730 {
731 m_BatchersContext = batcherContext;
732 m_DrawInstanceData = new CPUDrawInstanceData();
733 m_DrawInstanceData.Initialize();
734
735 m_BRG = new BatchRendererGroup(new BatchRendererGroupCreateInfo()
736 {
737 cullingCallback = OnPerformCulling,
738 finishedCullingCallback = onFinishedCulling,
739 userContext = IntPtr.Zero
740 });
741
742#if UNITY_EDITOR
743 if (desc.brgPicking != null)
744 {
745 var mat = new Material(desc.brgPicking);
746 mat.hideFlags = HideFlags.HideAndDontSave;
747 m_BRG.SetPickingMaterial(mat);
748 }
749 if (desc.brgLoading != null)
750 {
751 var mat = new Material(desc.brgLoading);
752 mat.hideFlags = HideFlags.HideAndDontSave;
753 m_BRG.SetLoadingMaterial(mat);
754 }
755 if (desc.brgError != null)
756 {
757 var mat = new Material(desc.brgError);
758 mat.hideFlags = HideFlags.HideAndDontSave;
759 m_BRG.SetErrorMaterial(mat);
760 }
761 var viewTypes = new BatchCullingViewType[] {
762 BatchCullingViewType.Light,
763 BatchCullingViewType.Camera,
764 BatchCullingViewType.Picking,
765 BatchCullingViewType.SelectionOutline,
766 BatchCullingViewType.Filtering
767 };
768 m_BRG.SetEnabledViewTypes(viewTypes);
769#endif
770
771 m_Culler = new InstanceCuller();
772 m_Culler.Init(batcherContext.resources, batcherContext.debugStats);
773
774 m_CachedInstanceDataBufferLayoutVersion = -1;
775 m_OnCompleteCallback = desc.onCompleteCallback;
776 m_BatchMaterialHash = new NativeParallelHashMap<int, BatchMaterialID>(64, Allocator.Persistent);
777 m_BatchMeshHash = new NativeParallelHashMap<int, BatchMeshID>(64, Allocator.Persistent);
778
779 m_GlobalBatchIDs = new NativeParallelHashMap<uint, BatchID>(6, Allocator.Persistent);
780 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.Default, GetBatchID(InstanceComponentGroup.Default));
781 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.DefaultWind, GetBatchID(InstanceComponentGroup.DefaultWind));
782 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.DefaultLightProbe, GetBatchID(InstanceComponentGroup.DefaultLightProbe));
783 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.DefaultLightmap, GetBatchID(InstanceComponentGroup.DefaultLightmap));
784 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.DefaultWindLightProbe, GetBatchID(InstanceComponentGroup.DefaultWindLightProbe));
785 m_GlobalBatchIDs.Add((uint)InstanceComponentGroup.DefaultWindLightmap, GetBatchID(InstanceComponentGroup.DefaultWindLightmap));
786 }
787
788 internal ref InstanceCuller culler => ref m_Culler;
789
790 public void Dispose()
791 {
792 m_OnCompleteCallback = null;
793 m_Culler.Dispose();
794
795 foreach (var batchID in m_GlobalBatchIDs)
796 {
797 if (!batchID.Value.Equals(BatchID.Null))
798 m_BRG.RemoveBatch(batchID.Value);
799 }
800 m_GlobalBatchIDs.Dispose();
801
802 if (m_BRG != null)
803 m_BRG.Dispose();
804
805 m_DrawInstanceData.Dispose();
806 m_DrawInstanceData = null;
807
808 m_BatchMaterialHash.Dispose();
809 m_BatchMeshHash.Dispose();
810 }
811
812 private BatchID GetBatchID(InstanceComponentGroup componentsOverriden)
813 {
814 if (m_CachedInstanceDataBufferLayoutVersion != m_BatchersContext.instanceDataBufferLayoutVersion)
815 return BatchID.Null;
816
817 Assert.IsTrue(m_BatchersContext.defaultDescriptions.Length == m_BatchersContext.defaultMetadata.Length);
818
819 const uint kClearIsOverriddenBit = 0x4FFFFFFF;
820 var tempMetadata = new NativeList<MetadataValue>(m_BatchersContext.defaultMetadata.Length, Allocator.Temp);
821
822 for(int i = 0; i < m_BatchersContext.defaultDescriptions.Length; ++i)
823 {
824 var componentGroup = m_BatchersContext.defaultDescriptions[i].componentGroup;
825 var metadata = m_BatchersContext.defaultMetadata[i];
826 var value = metadata.Value;
827
828 // if instances in this batch do not override the component, clear the override bit
829 if ((componentsOverriden & componentGroup) == 0)
830 value &= kClearIsOverriddenBit;
831
832 tempMetadata.Add(new MetadataValue
833 {
834 NameID = metadata.NameID,
835 Value = value
836 });
837 }
838
839 return m_BRG.AddBatch(tempMetadata.AsArray(), m_BatchersContext.gpuInstanceDataBuffer.bufferHandle);
840 }
841
842 private void UpdateInstanceDataBufferLayoutVersion()
843 {
844 if (m_CachedInstanceDataBufferLayoutVersion != m_BatchersContext.instanceDataBufferLayoutVersion)
845 {
846 m_CachedInstanceDataBufferLayoutVersion = m_BatchersContext.instanceDataBufferLayoutVersion;
847
848 foreach (var componentsToBatchID in m_GlobalBatchIDs)
849 {
850 var batchID = componentsToBatchID.Value;
851 if (!batchID.Equals(BatchID.Null))
852 m_BRG.RemoveBatch(batchID);
853
854 var componentsOverriden = (InstanceComponentGroup)componentsToBatchID.Key;
855 componentsToBatchID.Value = GetBatchID(componentsOverriden);
856 }
857 }
858 }
859
860 public CPUDrawInstanceData GetDrawInstanceData()
861 {
862 return m_DrawInstanceData;
863 }
864
865 public unsafe JobHandle OnPerformCulling(
866 BatchRendererGroup rendererGroup,
867 BatchCullingContext cc,
868 BatchCullingOutput cullingOutput,
869 IntPtr userContext)
870 {
871 foreach (var batchID in m_GlobalBatchIDs)
872 {
873 if (batchID.Value.Equals(BatchID.Null))
874 return new JobHandle();
875 }
876
877 m_DrawInstanceData.RebuildDrawListsIfNeeded();
878
879 bool allowOcclusionCulling = m_BatchersContext.hasBoundingSpheres;
880 JobHandle jobHandle = m_Culler.CreateCullJobTree(
881 cc,
882 cullingOutput,
883 m_BatchersContext.instanceData,
884 m_BatchersContext.sharedInstanceData,
885 m_BatchersContext.instanceDataBuffer,
886 m_BatchersContext.lodGroupCullingData,
887 m_DrawInstanceData,
888 m_GlobalBatchIDs,
889 m_BatchersContext.crossfadedRendererCount,
890 m_BatchersContext.smallMeshScreenPercentage,
891 allowOcclusionCulling ? m_BatchersContext.occlusionCullingCommon : null);
892
893 if (m_OnCompleteCallback != null)
894 m_OnCompleteCallback(jobHandle, cc, cullingOutput);
895
896 return jobHandle;
897 }
898
899 public void OnFinishedCulling(IntPtr customCullingResult)
900 {
901 int viewInstanceID = (int)customCullingResult;
902 m_Culler.EnsureValidOcclusionTestResults(viewInstanceID);
903 }
904
905 public void DestroyInstances(NativeArray<InstanceHandle> instances)
906 {
907 if (instances.Length == 0)
908 return;
909
910 Profiler.BeginSample("DestroyInstances");
911
912 m_DrawInstanceData.DestroyDrawInstances(instances);
913
914 Profiler.EndSample();
915 }
916
917 public void DestroyMaterials(NativeArray<int> destroyedMaterials)
918 {
919 if (destroyedMaterials.Length == 0)
920 return;
921
922 Profiler.BeginSample("DestroyMaterials");
923
924 var destroyedBatchMaterials = new NativeList<uint>(destroyedMaterials.Length, Allocator.TempJob);
925
926 foreach (int destroyedMaterial in destroyedMaterials)
927 {
928 if (m_BatchMaterialHash.TryGetValue(destroyedMaterial, out var destroyedBatchMaterial))
929 {
930 destroyedBatchMaterials.Add(destroyedBatchMaterial.value);
931 m_BatchMaterialHash.Remove(destroyedMaterial);
932 m_BRG.UnregisterMaterial(destroyedBatchMaterial);
933 }
934 }
935
936 m_DrawInstanceData.DestroyMaterialDrawInstances(destroyedBatchMaterials.AsArray());
937
938 destroyedBatchMaterials.Dispose();
939
940 Profiler.EndSample();
941 }
942
943 public void DestroyMeshes(NativeArray<int> destroyedMeshes)
944 {
945 if (destroyedMeshes.Length == 0)
946 return;
947
948 Profiler.BeginSample("DestroyMeshes");
949
950 foreach (int destroyedMesh in destroyedMeshes)
951 {
952 if (m_BatchMeshHash.TryGetValue(destroyedMesh, out var destroyedBatchMesh))
953 {
954 m_BatchMeshHash.Remove(destroyedMesh);
955 m_BRG.UnregisterMesh(destroyedBatchMesh);
956 }
957 }
958
959 Profiler.EndSample();
960 }
961
962 public void PostCullBeginCameraRendering(RenderRequestBatcherContext context)
963 {
964 }
965
966 private void RegisterBatchMeshes(NativeArray<int> meshIDs)
967 {
968 var newMeshIDs = new NativeList<int>(meshIDs.Length, Allocator.TempJob);
969 new FindNonRegisteredInstancesJob<BatchMeshID>
970 {
971 instanceIDs = meshIDs,
972 hashMap = m_BatchMeshHash,
973 outInstancesWriter = newMeshIDs.AsParallelWriter()
974 }
975 .ScheduleBatch(meshIDs.Length, FindNonRegisteredInstancesJob<BatchMeshID>.k_BatchSize).Complete();
976 var newBatchMeshIDs = new NativeArray<BatchMeshID>(newMeshIDs.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
977 m_BRG.RegisterMeshes(newMeshIDs.AsArray(), newBatchMeshIDs);
978
979 int totalMeshesNum = m_BatchMeshHash.Count() + newBatchMeshIDs.Length;
980 m_BatchMeshHash.Capacity = Math.Max(m_BatchMeshHash.Capacity, Mathf.CeilToInt(totalMeshesNum / 1023.0f) * 1024);
981
982 new RegisterNewInstancesJob<BatchMeshID>
983 {
984 instanceIDs = newMeshIDs.AsArray(),
985 batchIDs = newBatchMeshIDs,
986 hashMap = m_BatchMeshHash.AsParallelWriter()
987 }
988 .Schedule(newMeshIDs.Length, RegisterNewInstancesJob<BatchMeshID>.k_BatchSize).Complete();
989
990 newMeshIDs.Dispose();
991 newBatchMeshIDs.Dispose();
992 }
993
994 private void RegisterBatchMaterials(in NativeArray<int> usedMaterialIDs)
995 {
996 var newMaterialIDs = new NativeList<int>(usedMaterialIDs.Length, Allocator.TempJob);
997 new FindNonRegisteredInstancesJob<BatchMaterialID>
998 {
999 instanceIDs = usedMaterialIDs,
1000 hashMap = m_BatchMaterialHash,
1001 outInstancesWriter = newMaterialIDs.AsParallelWriter()
1002 }
1003 .ScheduleBatch(usedMaterialIDs.Length, FindNonRegisteredInstancesJob<BatchMaterialID>.k_BatchSize).Complete();
1004
1005 var newBatchMaterialIDs = new NativeArray<BatchMaterialID>(newMaterialIDs.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1006 m_BRG.RegisterMaterials(newMaterialIDs.AsArray(), newBatchMaterialIDs);
1007
1008 int totalMaterialsNum = m_BatchMaterialHash.Count() + newMaterialIDs.Length;
1009 m_BatchMaterialHash.Capacity = Math.Max(m_BatchMaterialHash.Capacity, Mathf.CeilToInt(totalMaterialsNum / 1023.0f) * 1024);
1010
1011 new RegisterNewInstancesJob<BatchMaterialID>
1012 {
1013 instanceIDs = newMaterialIDs.AsArray(),
1014 batchIDs = newBatchMaterialIDs,
1015 hashMap = m_BatchMaterialHash.AsParallelWriter()
1016 }
1017 .Schedule(newMaterialIDs.Length, RegisterNewInstancesJob<BatchMaterialID>.k_BatchSize).Complete();
1018
1019 newMaterialIDs.Dispose();
1020 newBatchMaterialIDs.Dispose();
1021 }
1022
1023 public void BuildBatch(
1024 NativeArray<InstanceHandle> instances,
1025 NativeArray<int> usedMaterialIDs,
1026 NativeArray<int> usedMeshIDs,
1027 in GPUDrivenRendererGroupData rendererData)
1028 {
1029 RegisterBatchMaterials(usedMaterialIDs);
1030 RegisterBatchMeshes(usedMeshIDs);
1031
1032 new CreateDrawBatchesJob
1033 {
1034 implicitInstanceIndices = rendererData.instancesCount.Length == 0,
1035 instances = instances,
1036 rendererData = rendererData,
1037 batchMeshHash = m_BatchMeshHash,
1038 batchMaterialHash = m_BatchMaterialHash,
1039 rangeHash = m_DrawInstanceData.rangeHash,
1040 drawRanges = m_DrawInstanceData.drawRanges,
1041 batchHash = m_DrawInstanceData.batchHash,
1042 drawBatches = m_DrawInstanceData.drawBatches,
1043 drawInstances = m_DrawInstanceData.drawInstances
1044 }.Run();
1045
1046 m_DrawInstanceData.NeedsRebuild();
1047 UpdateInstanceDataBufferLayoutVersion();
1048 }
1049
1050 public void InstanceOccludersUpdated(int viewInstanceID, int subviewMask)
1051 {
1052 m_Culler.InstanceOccludersUpdated(viewInstanceID, subviewMask, m_BatchersContext);
1053 }
1054
1055 public void UpdateFrame()
1056 {
1057 m_Culler.UpdateFrame();
1058 }
1059
1060 public ParallelBitArray GetCompactedVisibilityMasks(bool syncCullingJobs)
1061 {
1062 return m_Culler.GetCompactedVisibilityMasks(syncCullingJobs);
1063 }
1064
1065 public void OnEndContextRendering()
1066 {
1067 ParallelBitArray compactedVisibilityMasks = GetCompactedVisibilityMasks(syncCullingJobs: true);
1068
1069 if(compactedVisibilityMasks.IsCreated)
1070 m_BatchersContext.UpdatePerFrameInstanceVisibility(compactedVisibilityMasks);
1071 }
1072
1073 public void OnBeginCameraRendering(Camera camera)
1074 {
1075 m_Culler.OnBeginCameraRendering(camera);
1076 }
1077
1078 public void OnEndCameraRendering(Camera camera)
1079 {
1080 m_Culler.OnEndCameraRendering(camera);
1081 }
1082 }
1083}