A game about forced loneliness, made by TACStudios
1using System;
2using System.Threading;
3using UnityEngine.Assertions;
4using Unity.Burst;
5using Unity.Burst.CompilerServices;
6using Unity.Mathematics;
7using Unity.Collections;
8using Unity.Collections.LowLevel.Unsafe;
9using Unity.Jobs;
10using Unity.Jobs.LowLevel.Unsafe;
11using UnityEngine.SceneManagement;
12using UnityEngine.Rendering.RenderGraphModule;
13
14#if UNITY_EDITOR
15using UnityEditor;
16using UnityEditor.Rendering;
17using UnityEditor.SceneManagement;
18#endif
19
20namespace UnityEngine.Rendering
21{
22 internal struct RangeKey : IEquatable<RangeKey>
23 {
24 public byte layer;
25 public uint renderingLayerMask;
26 public MotionVectorGenerationMode motionMode;
27 public ShadowCastingMode shadowCastingMode;
28 public bool staticShadowCaster;
29 public int rendererPriority;
30 public bool supportsIndirect;
31
32 public bool Equals(RangeKey other)
33 {
34 return
35 layer == other.layer &&
36 renderingLayerMask == other.renderingLayerMask &&
37 motionMode == other.motionMode &&
38 shadowCastingMode == other.shadowCastingMode &&
39 staticShadowCaster == other.staticShadowCaster &&
40 rendererPriority == other.rendererPriority &&
41 supportsIndirect == other.supportsIndirect;
42 }
43
44 public override int GetHashCode()
45 {
46 int hash = 13;
47 hash = (hash * 23) + layer;
48 hash = (hash * 23) + (int)renderingLayerMask;
49 hash = (hash * 23) + (int)motionMode;
50 hash = (hash * 23) + (int)shadowCastingMode;
51 hash = (hash * 23) + (staticShadowCaster ? 1 : 0);
52 hash = (hash * 23) + rendererPriority;
53 hash = (hash * 23) + (supportsIndirect ? 1 : 0);
54 return hash;
55 }
56 }
57
58 internal struct DrawRange
59 {
60 public RangeKey key;
61 public int drawCount;
62 public int drawOffset;
63 }
64
65 internal struct DrawKey : IEquatable<DrawKey>
66 {
67 public BatchMeshID meshID;
68 public int submeshIndex;
69 public BatchMaterialID materialID;
70 public BatchDrawCommandFlags flags;
71 public int transparentInstanceId; // non-zero for transparent instances, to ensure each instance has its own draw command (for sorting)
72 public uint overridenComponents;
73 public RangeKey range;
74 public int lightmapIndex;
75
76 public bool Equals(DrawKey other)
77 {
78 return
79 meshID == other.meshID &&
80 submeshIndex == other.submeshIndex &&
81 materialID == other.materialID &&
82 flags == other.flags &&
83 transparentInstanceId == other.transparentInstanceId &&
84 overridenComponents == other.overridenComponents &&
85 range.Equals(other.range) &&
86 lightmapIndex == other.lightmapIndex;
87 }
88
89 public override int GetHashCode()
90 {
91 int hash = 13;
92 hash = (hash * 23) + (int)meshID.value;
93 hash = (hash * 23) + (int)submeshIndex;
94 hash = (hash * 23) + (int)materialID.value;
95 hash = (hash * 23) + (int)flags;
96 hash = (hash * 23) + transparentInstanceId;
97 hash = (hash * 23) + range.GetHashCode();
98 hash = (hash * 23) + (int)overridenComponents;
99 hash = (hash * 23) + lightmapIndex;
100 return hash;
101 }
102 }
103
104 internal struct DrawBatch
105 {
106 public DrawKey key;
107 public int instanceCount;
108 public int instanceOffset;
109 public MeshProceduralInfo procInfo;
110 }
111
112 internal struct DrawInstance
113 {
114 public DrawKey key;
115 public int instanceIndex;
116 }
117
118 internal struct BinningConfig
119 {
120 public int viewCount;
121 public bool supportsCrossFade;
122 public bool supportsMotionCheck;
123
124 public int visibilityConfigCount
125 {
126 get
127 {
128 // always bin based on flip winding state (the initial 1 bit)
129 int bitCount = 1 + viewCount + (supportsCrossFade ? 1 : 0) + (supportsMotionCheck ? 1 : 0);
130 return 1 << bitCount;
131 }
132 }
133 }
134
135 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
136 internal struct CullingJob : IJobParallelFor
137 {
138 public const int k_BatchSize = 32;
139
140 const uint k_LODFadeZeroPacked = 127;
141
142 const float k_LODPercentInvisible = 0.0f;
143 const float k_LODPercentFullyVisible = 1.0f;
144 const float k_LODPercentSpeedTree = 2.0f;
145
146 const float k_SmallMeshTransitionWidth = 0.1f;
147
148 enum CrossFadeType
149 {
150 kDisabled,
151 kCrossFadeOut, // 1 == instance is visible in current lod, and not next - could be fading out
152 kCrossFadeIn, // 2 == instance is visivle in next lod level, but not current - could be fading in
153 kVisible // 3 == instance is visible in both current and next lod level - could not be impacted by fade
154 }
155
156 [ReadOnly] public BinningConfig binningConfig;
157
158 [ReadOnly] public BatchCullingViewType viewType;
159 [ReadOnly] public float3 cameraPosition;
160 [ReadOnly] public float sqrScreenRelativeMetric;
161 [ReadOnly] public float minScreenRelativeHeight;
162 [ReadOnly] public bool isOrtho;
163 [ReadOnly] public bool cullLightmappedShadowCasters;
164 [ReadOnly] public int maxLOD;
165 [ReadOnly] public uint cullingLayerMask;
166 [ReadOnly] public ulong sceneCullingMask;
167
168 [ReadOnly] public NativeArray<FrustumPlaneCuller.PlanePacket4> frustumPlanePackets;
169 [ReadOnly] public NativeArray<FrustumPlaneCuller.SplitInfo> frustumSplitInfos;
170 [ReadOnly] public NativeArray<Plane> lightFacingFrustumPlanes;
171 [ReadOnly] public NativeArray<ReceiverSphereCuller.SplitInfo> receiverSplitInfos;
172 public float3x3 worldToLightSpaceRotation;
173
174 [ReadOnly] public CPUInstanceData.ReadOnly instanceData;
175 [ReadOnly] public CPUSharedInstanceData.ReadOnly sharedInstanceData;
176 [NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<LODGroupCullingData> lodGroupCullingData;
177 [NativeDisableUnsafePtrRestriction] [ReadOnly] public IntPtr occlusionBuffer;
178
179 [NativeDisableParallelForRestriction][WriteOnly] public NativeArray<byte> rendererVisibilityMasks;
180 [NativeDisableParallelForRestriction][WriteOnly] public NativeArray<byte> rendererCrossFadeValues;
181
182
183 // float [-1.0f... 1.0f] -> uint [0...254]
184 static uint PackFloatToUint8(float percent)
185 {
186 uint packed = (uint)((1.0f + percent) * 127.0f + 0.5f);
187 // avoid zero
188 if (percent < 0.0f)
189 packed = math.clamp(packed, 0, 126);
190 else
191 packed = math.clamp(packed, 128, 254);
192 return packed;
193 }
194
195 unsafe float CalculateLODVisibility(int instanceIndex, int sharedInstanceIndex, InstanceFlags instanceFlags)
196 {
197 var lodPercent = k_LODPercentFullyVisible;
198 var lodDataIndexAndMask = sharedInstanceData.lodGroupAndMasks[sharedInstanceIndex];
199
200 if (lodDataIndexAndMask != 0xFFFFFFFF)
201 {
202 lodPercent = k_LODPercentInvisible;
203
204 var lodIndex = lodDataIndexAndMask >> 8;
205 var lodMask = lodDataIndexAndMask & 0xFF;
206 Assert.IsTrue(lodMask > 0);
207
208 ref var lodGroup = ref lodGroupCullingData.ElementAt((int)lodIndex);
209 float cameraSqrDistToLODCenter = isOrtho ? sqrScreenRelativeMetric : LODGroupRenderingUtils.CalculateSqrPerspectiveDistance(lodGroup.worldSpaceReferencePoint, cameraPosition, sqrScreenRelativeMetric);
210
211 // Remove lods that are beyond the max lod.
212 uint maxLodMask = 0xffffffff << maxLOD;
213 lodMask &= maxLodMask;
214
215 // Offset to the lod preceding the first for proper cross fade calculation.
216 int m = math.max(math.tzcnt(lodMask) - 1, maxLOD);
217 lodMask >>= m;
218
219 while (lodMask > 0)
220 {
221 var lodRangeSqrMin = m == maxLOD ? 0.0f : lodGroup.sqrDistances[m - 1];
222 var lodRangeSqrMax = lodGroup.sqrDistances[m];
223
224 // Camera is beyond the range of this all further lods. No need to proceed.
225 if (cameraSqrDistToLODCenter < lodRangeSqrMin)
226 break;
227
228 // Instance is in the min/max range of this lod. Proceeding.
229 if (cameraSqrDistToLODCenter < lodRangeSqrMax)
230 {
231 var type = (CrossFadeType)(lodMask & 3);
232
233 // Instance is in this and/or the next lod.
234 if (type != CrossFadeType.kDisabled)
235 {
236 // Instance is in both this and the next lod. No need to fade.
237 if (type == CrossFadeType.kVisible)
238 {
239 lodPercent = k_LODPercentFullyVisible;
240 }
241 else
242 {
243 var distanceToLodCenter = math.sqrt(cameraSqrDistToLODCenter);
244 var maxDist = math.sqrt(lodRangeSqrMax);
245
246 // SpeedTree cross fade.
247 if (lodGroup.percentageFlags[m])
248 {
249 // The fading-in instance is not visible but the fading-out is visible and it does the speed tree vertex deformation.
250
251 if (type == CrossFadeType.kCrossFadeIn)
252 {
253 lodPercent = k_LODPercentInvisible;
254 }
255 else if (type == CrossFadeType.kCrossFadeOut)
256 {
257 var minDist = m > 0 ? math.sqrt(lodGroup.sqrDistances[m - 1]) : lodGroup.worldSpaceSize;
258 lodPercent = k_LODPercentSpeedTree + math.max(distanceToLodCenter - minDist, 0.0f) / (maxDist - minDist);
259 }
260 }
261 // Dithering cross fade.
262 else
263 {
264 // If in the transition zone, both fading-in and fading-out instances are visible. Calculate the lod percent.
265 // If not then only the fading-out instance is fully visible, and fading-in is invisible.
266
267 var transitionDist = lodGroup.transitionDistances[m];
268 var dif = maxDist - distanceToLodCenter;
269
270 if (dif < transitionDist)
271 {
272 lodPercent = dif / transitionDist;
273
274 if (type == CrossFadeType.kCrossFadeIn)
275 lodPercent = -lodPercent;
276 }
277 else if (type == CrossFadeType.kCrossFadeOut)
278 {
279 lodPercent = k_LODPercentFullyVisible;
280 }
281 }
282 }
283 }
284
285 // We found the lod and the percentage.
286 break;
287 }
288
289 ++m;
290 lodMask >>= 1;
291 }
292 }
293 else if(viewType < BatchCullingViewType.SelectionOutline && (instanceFlags & InstanceFlags.SmallMeshCulling) != 0)
294 {
295 ref readonly AABB worldAABB = ref instanceData.worldAABBs.UnsafeElementAt(instanceIndex);
296 var cameraSqrDist = isOrtho ? sqrScreenRelativeMetric : LODGroupRenderingUtils.CalculateSqrPerspectiveDistance(worldAABB.center, cameraPosition, sqrScreenRelativeMetric);
297 var cameraDist = math.sqrt(cameraSqrDist);
298
299 var aabbSize = worldAABB.extents * 2.0f;
300 var worldSpaceSize = math.max(math.max(aabbSize.x, aabbSize.y), aabbSize.z);
301 var maxDist = LODGroupRenderingUtils.CalculateLODDistance(minScreenRelativeHeight, worldSpaceSize);
302
303 var transitionHeight = minScreenRelativeHeight + k_SmallMeshTransitionWidth * minScreenRelativeHeight;
304 var fadeOutRange = Mathf.Max(0.0f,maxDist - LODGroupRenderingUtils.CalculateLODDistance(transitionHeight, worldSpaceSize));
305
306 lodPercent = math.saturate((maxDist - cameraDist) / fadeOutRange);
307 }
308
309 return lodPercent;
310 }
311
312 private unsafe uint CalculateVisibilityMask(int instanceIndex, int sharedInstanceIndex, InstanceFlags instanceFlags)
313 {
314 if (cullingLayerMask == 0)
315 return 0;
316
317 if ((cullingLayerMask & (1 << sharedInstanceData.gameObjectLayers[sharedInstanceIndex])) == 0)
318 return 0;
319
320 if (cullLightmappedShadowCasters && (instanceFlags & InstanceFlags.AffectsLightmaps) != 0)
321 return 0;
322
323#if UNITY_EDITOR
324 if ((sceneCullingMask & instanceData.editorData.sceneCullingMasks[instanceIndex]) == 0)
325 return 0;
326
327 if(viewType == BatchCullingViewType.SelectionOutline && !instanceData.editorData.selectedBits.Get(instanceIndex))
328 return 0;
329#endif
330
331 // cull early for camera and shadow views based on the shadow culling mode
332 if (viewType == BatchCullingViewType.Camera && (instanceFlags & InstanceFlags.IsShadowsOnly) != 0)
333 return 0;
334 if (viewType == BatchCullingViewType.Light && (instanceFlags & InstanceFlags.IsShadowsOff) != 0)
335 return 0;
336
337 ref readonly AABB worldAABB = ref instanceData.worldAABBs.UnsafeElementAt(instanceIndex);
338 uint visibilityMask = FrustumPlaneCuller.ComputeSplitVisibilityMask(frustumPlanePackets, frustumSplitInfos, worldAABB);
339
340 if (visibilityMask != 0 && receiverSplitInfos.Length > 0)
341 visibilityMask &= ReceiverSphereCuller.ComputeSplitVisibilityMask(lightFacingFrustumPlanes, receiverSplitInfos, worldToLightSpaceRotation, worldAABB);
342
343 // Perform an occlusion test on the instance bounds if we have an occlusion buffer available and the instance is still visible
344 if (visibilityMask != 0 && occlusionBuffer != IntPtr.Zero)
345 visibilityMask = BatchRendererGroup.OcclusionTestAABB(occlusionBuffer, worldAABB.ToBounds()) ? visibilityMask : 0;
346
347 return visibilityMask;
348 }
349
350 public void Execute(int instanceIndex)
351 {
352 InstanceHandle instance = instanceData.instances[instanceIndex];
353 int sharedInstanceIndex = sharedInstanceData.InstanceToIndex(instanceData, instance);
354 var instanceFlags = sharedInstanceData.flags[sharedInstanceIndex].instanceFlags;
355
356 var visibilityMask = CalculateVisibilityMask(instanceIndex, sharedInstanceIndex, instanceFlags);
357 var crossFadeValue = k_LODFadeZeroPacked;
358
359 if (visibilityMask != 0)
360 {
361 float lodPercent = CalculateLODVisibility(instanceIndex, sharedInstanceIndex, instanceFlags);
362
363 if (lodPercent != k_LODPercentInvisible)
364 {
365 if (binningConfig.supportsMotionCheck)
366 {
367 bool hasMotion = instanceData.movedInPreviousFrameBits.Get(instanceIndex);
368 visibilityMask = (visibilityMask << 1) | (hasMotion ? 1U : 0);
369 }
370
371 if (binningConfig.supportsCrossFade)
372 {
373 bool hasDitheringCrossFade = false;
374
375 if (lodPercent != k_LODPercentFullyVisible)
376 {
377 bool isSpeedTreeCrossFade = lodPercent >= k_LODPercentSpeedTree;
378
379 // If this is a speed tree cross fade then we provide cross fade value but we don't enable cross fade keyword.
380 if (isSpeedTreeCrossFade)
381 lodPercent -= k_LODPercentSpeedTree;
382 else
383 hasDitheringCrossFade = true;
384
385 crossFadeValue = PackFloatToUint8(lodPercent);
386 }
387
388 visibilityMask = (visibilityMask << 1) | (hasDitheringCrossFade ? 1U : 0);
389 }
390 }
391 else
392 {
393 visibilityMask = 0;
394 }
395 }
396
397 rendererVisibilityMasks[instance.index] = (byte)visibilityMask;
398 rendererCrossFadeValues[instance.index] = (byte)crossFadeValue;
399 }
400 }
401
402 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
403 internal unsafe struct AllocateBinsPerBatch : IJobParallelFor
404 {
405 [ReadOnly] public BinningConfig binningConfig;
406
407 [ReadOnly] public NativeList<DrawBatch> drawBatches;
408 [ReadOnly] public NativeArray<int> drawInstanceIndices;
409 [ReadOnly] public CPUInstanceData.ReadOnly instanceData;
410 [ReadOnly] public NativeArray<byte> rendererVisibilityMasks;
411
412 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> batchBinAllocOffsets;
413 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> batchBinCounts;
414
415 [NativeDisableContainerSafetyRestriction, NoAlias] [DeallocateOnJobCompletion] public NativeArray<int> binAllocCounter;
416 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<short> binConfigIndices;
417 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> binVisibleInstanceCounts;
418
419 [ReadOnly] public int debugCounterIndexBase;
420 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> splitDebugCounters;
421
422 bool IsInstanceFlipped(int rendererIndex)
423 {
424 InstanceHandle instance = InstanceHandle.FromInt(rendererIndex);
425 int instanceIndex = instanceData.InstanceToIndex(instance);
426 return instanceData.localToWorldIsFlippedBits.Get(instanceIndex);
427 }
428
429 unsafe public void Execute(int batchIndex)
430 {
431 // figure out how many combinations of views/features we need to partition by
432 int configCount = binningConfig.visibilityConfigCount;
433
434 // allocate space to keep track of the number of instances per config
435 var visibleCountPerConfig = stackalloc int[configCount];
436 for (int i = 0; i < configCount; ++i)
437 visibleCountPerConfig[i] = 0;
438
439 // and space to keep track of which configs have any instances
440 int configMaskCount = (configCount + 63)/64;
441 var configUsedMasks = stackalloc UInt64[configMaskCount];
442 for (int i = 0; i < configMaskCount; ++i)
443 configUsedMasks[i] = 0;
444
445 // loop over all instances within this batch
446 var drawBatch = drawBatches[batchIndex];
447 var instanceCount = drawBatch.instanceCount;
448 var instanceOffset = drawBatch.instanceOffset;
449 for (int i = 0; i < instanceCount; ++i)
450 {
451 var rendererIndex = drawInstanceIndices[instanceOffset + i];
452
453 bool isFlipped = IsInstanceFlipped(rendererIndex);
454 int visibilityMask = (int)rendererVisibilityMasks[rendererIndex];
455 if (visibilityMask == 0)
456 continue;
457
458 int configIndex = (int)(visibilityMask << 1) | (isFlipped ? 1 : 0);
459 Assert.IsTrue(configIndex < configCount);
460 visibleCountPerConfig[configIndex]++;
461 configUsedMasks[configIndex >> 6] |= 1ul << (configIndex & 0x3f);
462 }
463
464 // allocate and store the non-empty configs as bins
465 int binCount = 0;
466 for (int i = 0; i < configMaskCount; ++i)
467 binCount += math.countbits(configUsedMasks[i]);
468
469 int allocOffsetStart = 0;
470 if (binCount > 0)
471 {
472 var drawCommandCountPerView = stackalloc int[binningConfig.viewCount];
473 var visibleCountPerView = stackalloc int[binningConfig.viewCount];
474 for (int i = 0; i < binningConfig.viewCount; ++i)
475 {
476 drawCommandCountPerView[i] = 0;
477 visibleCountPerView[i] = 0;
478 }
479
480 bool countVisibilityStats = (debugCounterIndexBase >= 0);
481 int shiftForVisibilityMask = 1 + (binningConfig.supportsMotionCheck ? 1 : 0) + (binningConfig.supportsCrossFade ? 1 : 0);
482
483 int *allocCounter = (int *)binAllocCounter.GetUnsafePtr<int>();
484 int allocOffsetEnd = Interlocked.Add(ref UnsafeUtility.AsRef<int>(allocCounter), binCount);
485 allocOffsetStart = allocOffsetEnd - binCount;
486
487 int allocOffset = allocOffsetStart;
488 for (int i = 0; i < configMaskCount; ++i)
489 {
490 UInt64 configRemainMask = configUsedMasks[i];
491 while (configRemainMask != 0)
492 {
493 var bitPos = math.tzcnt(configRemainMask);
494 configRemainMask ^= 1ul << bitPos;
495
496 int configIndex = 64*i + bitPos;
497 int visibleCount = visibleCountPerConfig[configIndex];
498 Assert.IsTrue(visibleCount > 0);
499
500 binConfigIndices[allocOffset] = (short)configIndex;
501 binVisibleInstanceCounts[allocOffset] = visibleCount;
502 allocOffset++;
503
504 int visibilityMask = countVisibilityStats ? (configIndex >> shiftForVisibilityMask) : 0;
505 while (visibilityMask != 0)
506 {
507 var viewIndex = math.tzcnt(visibilityMask);
508 visibilityMask ^= 1 << viewIndex;
509
510 drawCommandCountPerView[viewIndex] += 1;
511 visibleCountPerView[viewIndex] += visibleCount;
512 }
513 }
514 }
515 Assert.IsTrue(allocOffset == allocOffsetEnd);
516
517 if (countVisibilityStats)
518 {
519 for (int viewIndex = 0; viewIndex < binningConfig.viewCount; ++viewIndex)
520 {
521 int* counterPtr = (int*)splitDebugCounters.GetUnsafePtr() + (debugCounterIndexBase + viewIndex) * (int)InstanceCullerSplitDebugCounter.Count;
522
523 int drawCommandCount = drawCommandCountPerView[viewIndex];
524 if (drawCommandCount > 0)
525 Interlocked.Add(ref UnsafeUtility.AsRef<int>(counterPtr + (int)InstanceCullerSplitDebugCounter.DrawCommands), drawCommandCount);
526
527 int visibleCount = visibleCountPerView[viewIndex];
528 if (visibleCount > 0)
529 Interlocked.Add(ref UnsafeUtility.AsRef<int>(counterPtr + (int)InstanceCullerSplitDebugCounter.VisibleInstances), visibleCount);
530 }
531 }
532 }
533 batchBinAllocOffsets[batchIndex] = allocOffsetStart;
534 batchBinCounts[batchIndex] = binCount;
535 }
536 }
537
538 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
539 internal unsafe struct PrefixSumDrawsAndInstances : IJob
540 {
541 [ReadOnly] public NativeList<DrawRange> drawRanges;
542 [ReadOnly] public NativeArray<int> drawBatchIndices;
543
544 [ReadOnly] public NativeArray<int> batchBinAllocOffsets;
545 [ReadOnly] public NativeArray<int> batchBinCounts;
546 [ReadOnly] public NativeArray<int> binVisibleInstanceCounts;
547
548 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> batchDrawCommandOffsets;
549 [NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> binVisibleInstanceOffsets;
550
551 [NativeDisableUnsafePtrRestriction] public NativeArray<BatchCullingOutputDrawCommands> cullingOutput;
552
553 [ReadOnly] public IndirectBufferLimits indirectBufferLimits;
554 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<IndirectBufferAllocInfo> indirectBufferAllocInfo;
555 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indirectAllocationCounters;
556
557 unsafe public void Execute()
558 {
559 BatchCullingOutputDrawCommands output = cullingOutput[0];
560
561 bool allowIndirect = indirectBufferLimits.maxInstanceCount > 0;
562
563 int outRangeIndex;
564 int outDirectCommandIndex;
565 int outDirectVisibleInstanceIndex;
566 int outIndirectCommandIndex;
567 int outIndirectVisibleInstanceIndex;
568
569 for (;;)
570 {
571 // reset counters
572 outRangeIndex = 0;
573 outDirectCommandIndex = 0;
574 outDirectVisibleInstanceIndex = 0;
575 outIndirectCommandIndex = 0;
576 outIndirectVisibleInstanceIndex = 0;
577
578 for (int rangeIndex = 0; rangeIndex < drawRanges.Length; ++rangeIndex)
579 {
580 var drawRangeInfo = drawRanges[rangeIndex];
581 bool isIndirect = allowIndirect && drawRangeInfo.key.supportsIndirect;
582
583 int rangeDrawCommandCount = 0;
584 int rangeDrawCommandOffset = isIndirect ? outIndirectCommandIndex : outDirectCommandIndex;
585
586 for (int drawIndexInRange = 0; drawIndexInRange < drawRangeInfo.drawCount; ++drawIndexInRange)
587 {
588 var batchIndex = drawBatchIndices[drawRangeInfo.drawOffset + drawIndexInRange];
589 var binAllocOffset = batchBinAllocOffsets[batchIndex];
590 var binCount = batchBinCounts[batchIndex];
591
592 if (isIndirect)
593 {
594 batchDrawCommandOffsets[batchIndex] = outIndirectCommandIndex;
595 outIndirectCommandIndex += binCount;
596 }
597 else
598 {
599 batchDrawCommandOffsets[batchIndex] = outDirectCommandIndex;
600 outDirectCommandIndex += binCount;
601 }
602 rangeDrawCommandCount += binCount;
603
604 for (int binIndexInBatch = 0; binIndexInBatch < binCount; ++binIndexInBatch)
605 {
606 var binIndex = binAllocOffset + binIndexInBatch;
607 if (isIndirect)
608 {
609 binVisibleInstanceOffsets[binIndex] = outIndirectVisibleInstanceIndex;
610 outIndirectVisibleInstanceIndex += binVisibleInstanceCounts[binIndex];
611 }
612 else
613 {
614 binVisibleInstanceOffsets[binIndex] = outDirectVisibleInstanceIndex;
615 outDirectVisibleInstanceIndex += binVisibleInstanceCounts[binIndex];
616 }
617 }
618 }
619
620 if (rangeDrawCommandCount != 0)
621 {
622#if DEBUG
623 if (outRangeIndex >= output.drawRangeCount)
624 throw new Exception("Exceeding draw range count");
625#endif
626
627 var rangeKey = drawRangeInfo.key;
628 output.drawRanges[outRangeIndex] = new BatchDrawRange
629 {
630 drawCommandsBegin = (uint)rangeDrawCommandOffset,
631 drawCommandsCount = (uint)rangeDrawCommandCount,
632 drawCommandsType = isIndirect ? BatchDrawCommandType.Indirect : BatchDrawCommandType.Direct,
633 filterSettings = new BatchFilterSettings
634 {
635 renderingLayerMask = rangeKey.renderingLayerMask,
636 rendererPriority = rangeKey.rendererPriority,
637 layer = rangeKey.layer,
638 batchLayer = isIndirect ? BatchLayer.InstanceCullingIndirect : BatchLayer.InstanceCullingDirect,
639 motionMode = rangeKey.motionMode,
640 shadowCastingMode = rangeKey.shadowCastingMode,
641 receiveShadows = true,
642 staticShadowCaster = rangeKey.staticShadowCaster,
643 allDepthSorted = false,
644 }
645 };
646 outRangeIndex++;
647 }
648 }
649
650 output.drawRangeCount = outRangeIndex; // trim to the number of written ranges
651
652 // try to allocate buffer space for indirect
653 bool isValid = true;
654 if (allowIndirect)
655 {
656 int* allocCounters = (int*)indirectAllocationCounters.GetUnsafePtr<int>();
657
658 var allocInfo = new IndirectBufferAllocInfo();
659 allocInfo.drawCount = outIndirectCommandIndex;
660 allocInfo.instanceCount = outIndirectVisibleInstanceIndex;
661
662 int drawAllocCount = allocInfo.drawCount + IndirectBufferContextStorage.kExtraDrawAllocationCount;
663 int drawAllocEnd = Interlocked.Add(ref UnsafeUtility.AsRef<int>(allocCounters + (int)IndirectAllocator.NextDrawIndex), drawAllocCount);
664 allocInfo.drawAllocIndex = drawAllocEnd - drawAllocCount;
665
666 int instanceAllocEnd = Interlocked.Add(ref UnsafeUtility.AsRef<int>(allocCounters + (int)IndirectAllocator.NextInstanceIndex), allocInfo.instanceCount);
667 allocInfo.instanceAllocIndex = instanceAllocEnd - allocInfo.instanceCount;
668
669 if (!allocInfo.IsWithinLimits(indirectBufferLimits))
670 {
671 allocInfo = new IndirectBufferAllocInfo();
672 isValid = false;
673 }
674
675 indirectBufferAllocInfo[0] = allocInfo;
676 }
677 if (isValid)
678 break;
679
680 // out of indirect memory, reset counters and try again without indirect
681 //Debug.Log("Out of indirect buffer space: falling back to direct draws for this frame!");
682 allowIndirect = false;
683 }
684
685 if (outDirectCommandIndex != 0)
686 {
687 output.drawCommandCount = outDirectCommandIndex;
688 output.drawCommands = MemoryUtilities.Malloc<BatchDrawCommand>(outDirectCommandIndex, Allocator.TempJob);
689
690 output.visibleInstanceCount = outDirectVisibleInstanceIndex;
691 output.visibleInstances = MemoryUtilities.Malloc<int>(outDirectVisibleInstanceIndex, Allocator.TempJob);
692 }
693 if (outIndirectCommandIndex != 0)
694 {
695 output.indirectDrawCommandCount = outIndirectCommandIndex;
696 output.indirectDrawCommands = MemoryUtilities.Malloc<BatchDrawCommandIndirect>(outIndirectCommandIndex, Allocator.TempJob);
697 }
698
699 int totalCommandCount = outDirectCommandIndex + outIndirectCommandIndex;
700 output.instanceSortingPositions = MemoryUtilities.Malloc<float>(3 * totalCommandCount, Allocator.TempJob);
701
702 cullingOutput[0] = output;
703 }
704 }
705
706 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
707 internal unsafe struct DrawCommandOutputPerBatch : IJobParallelFor
708 {
709 [ReadOnly] public BinningConfig binningConfig;
710 [ReadOnly] public NativeParallelHashMap<uint, BatchID> batchIDs;
711
712 [ReadOnly] public GPUInstanceDataBuffer.ReadOnly instanceDataBuffer;
713
714 [ReadOnly] public NativeList<DrawBatch> drawBatches;
715 [ReadOnly] public NativeArray<int> drawInstanceIndices;
716 [ReadOnly] public CPUInstanceData.ReadOnly instanceData;
717
718 [ReadOnly] public NativeArray<byte> rendererVisibilityMasks;
719 [ReadOnly] public NativeArray<byte> rendererCrossFadeValues;
720
721 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int> batchBinAllocOffsets;
722 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int> batchBinCounts;
723 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int> batchDrawCommandOffsets;
724
725 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<short> binConfigIndices;
726 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int> binVisibleInstanceOffsets;
727 [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<int> binVisibleInstanceCounts;
728
729 [ReadOnly] public NativeArray<BatchCullingOutputDrawCommands> cullingOutput;
730
731 [ReadOnly] public IndirectBufferLimits indirectBufferLimits;
732 [ReadOnly] public GraphicsBufferHandle visibleInstancesBufferHandle;
733 [ReadOnly] public GraphicsBufferHandle indirectArgsBufferHandle;
734 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<IndirectBufferAllocInfo> indirectBufferAllocInfo;
735 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<IndirectDrawInfo> indirectDrawInfoGlobalArray;
736 [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<IndirectInstanceInfo> indirectInstanceInfoGlobalArray;
737
738 unsafe int EncodeGPUInstanceIndexAndCrossFade(int rendererIndex, bool negateCrossFade)
739 {
740 var gpuInstanceIndex = instanceDataBuffer.CPUInstanceToGPUInstance(InstanceHandle.FromInt(rendererIndex));
741 int crossFadeValue = rendererCrossFadeValues[rendererIndex];
742 crossFadeValue -= 127;
743 if (negateCrossFade)
744 crossFadeValue = -crossFadeValue;
745 gpuInstanceIndex.index |= crossFadeValue << 24;
746 return gpuInstanceIndex.index;
747 }
748
749 bool IsInstanceFlipped(int rendererIndex)
750 {
751 InstanceHandle instance = InstanceHandle.FromInt(rendererIndex);
752 int instanceIndex = instanceData.InstanceToIndex(instance);
753 return instanceData.localToWorldIsFlippedBits.Get(instanceIndex);
754 }
755
756 unsafe public void Execute(int batchIndex)
757 {
758 DrawBatch drawBatch = drawBatches[batchIndex];
759
760 var binCount = batchBinCounts[batchIndex];
761 if (binCount == 0)
762 return;
763
764 BatchCullingOutputDrawCommands output = cullingOutput[0];
765
766 IndirectBufferAllocInfo indirectAllocInfo = new IndirectBufferAllocInfo();
767 if (indirectBufferLimits.maxDrawCount > 0)
768 indirectAllocInfo = indirectBufferAllocInfo[0];
769 bool allowIndirect = !indirectAllocInfo.IsEmpty();
770
771 bool isIndirect = allowIndirect && drawBatch.key.range.supportsIndirect;
772
773 // figure out how many combinations of views/features we need to partition by
774 int configCount = binningConfig.visibilityConfigCount;
775
776 // allocate storage for the instance offsets, set to zero
777 var instanceOffsetPerConfig = stackalloc int[configCount];
778 for (int i = 0; i < configCount; ++i)
779 instanceOffsetPerConfig[i] = 0;
780
781 // allocate storage to be able to look up the draw index per instance (by config)
782 var drawCommandOffsetPerConfig = stackalloc int[configCount];
783
784 // write the draw commands, scatter the allocated offsets to our storage
785 // TODO: fast path when binCount == 1
786 var batchBinAllocOffset = batchBinAllocOffsets[batchIndex];
787 var batchDrawCommandOffset = batchDrawCommandOffsets[batchIndex];
788 var lastBinInstanceOffset = 0;
789 bool rangeSupportsMotion = (drawBatch.key.range.motionMode == MotionVectorGenerationMode.Object ||
790 drawBatch.key.range.motionMode == MotionVectorGenerationMode.ForceNoMotion);
791 for (int binIndexInBatch = 0; binIndexInBatch < binCount; ++binIndexInBatch)
792 {
793 var binIndex = batchBinAllocOffset + binIndexInBatch;
794 var visibleInstanceOffset = binVisibleInstanceOffsets[binIndex];
795 var visibleInstanceCount = binVisibleInstanceCounts[binIndex];
796 lastBinInstanceOffset = visibleInstanceOffset;
797
798 // scatter to local storage for the per-instance loop below
799 var configIndex = binConfigIndices[binIndex];
800 instanceOffsetPerConfig[configIndex] = visibleInstanceOffset;
801
802 // get the write index for the draw command
803 var drawCommandOffset = batchDrawCommandOffset + binIndexInBatch;
804 drawCommandOffsetPerConfig[configIndex] = drawCommandOffset;
805
806 var drawFlags = drawBatch.key.flags;
807 bool isFlipped = ((configIndex & 1) != 0);
808 if (isFlipped)
809 drawFlags |= BatchDrawCommandFlags.FlipWinding;
810
811 int visibilityMask = configIndex >> 1;
812 if (binningConfig.supportsCrossFade)
813 {
814 if ((visibilityMask & 1) != 0)
815 drawFlags |= BatchDrawCommandFlags.LODCrossFadeKeyword;
816 visibilityMask >>= 1;
817 }
818 if (binningConfig.supportsMotionCheck)
819 {
820 if ((visibilityMask & 1) != 0 && rangeSupportsMotion)
821 drawFlags |= BatchDrawCommandFlags.HasMotion;
822 visibilityMask >>= 1;
823 }
824 Assert.IsTrue(visibilityMask != 0);
825
826 var sortingPosition = 0;
827 if ((drawFlags & BatchDrawCommandFlags.HasSortingPosition) != 0)
828 {
829 int globalCommandOffset = drawCommandOffset;
830 if (isIndirect)
831 globalCommandOffset += output.drawCommandCount; // skip over direct commands
832 sortingPosition = 3 * globalCommandOffset;
833 }
834
835#if DEBUG
836 if (!batchIDs.ContainsKey(drawBatch.key.overridenComponents))
837 throw new Exception("Draw command created with an invalid BatchID");
838#endif
839 if (isIndirect)
840 {
841#if DEBUG
842 if (drawCommandOffset >= output.indirectDrawCommandCount)
843 throw new Exception("Exceeding draw command count");
844#endif
845 int instanceInfoGlobalIndex = indirectAllocInfo.instanceAllocIndex + visibleInstanceOffset;
846 int drawInfoGlobalIndex = indirectAllocInfo.drawAllocIndex + drawCommandOffset;
847
848 indirectDrawInfoGlobalArray[drawInfoGlobalIndex] = new IndirectDrawInfo
849 {
850 indexCount = drawBatch.procInfo.indexCount,
851 firstIndex = drawBatch.procInfo.firstIndex,
852 baseVertex = drawBatch.procInfo.baseVertex,
853 firstInstanceGlobalIndex = (uint)instanceInfoGlobalIndex,
854 maxInstanceCount = (uint)visibleInstanceCount,
855 };
856 output.indirectDrawCommands[drawCommandOffset] = new BatchDrawCommandIndirect
857 {
858 flags = drawFlags,
859 visibleOffset = (uint)instanceInfoGlobalIndex,
860 batchID = batchIDs[drawBatch.key.overridenComponents],
861 materialID = drawBatch.key.materialID,
862 splitVisibilityMask = (ushort)visibilityMask,
863 lightmapIndex = (ushort)drawBatch.key.lightmapIndex,
864 sortingPosition = sortingPosition,
865 meshID = drawBatch.key.meshID,
866 topology = drawBatch.procInfo.topology,
867 visibleInstancesBufferHandle = visibleInstancesBufferHandle,
868 indirectArgsBufferHandle = indirectArgsBufferHandle,
869 indirectArgsBufferOffset = (uint)(drawInfoGlobalIndex * GraphicsBuffer.IndirectDrawIndexedArgs.size),
870 };
871 }
872 else
873 {
874#if DEBUG
875 if (drawCommandOffset >= output.drawCommandCount)
876 throw new Exception("Exceeding draw command count");
877#endif
878 output.drawCommands[drawCommandOffset] = new BatchDrawCommand
879 {
880 flags = drawFlags,
881 visibleOffset = (uint)visibleInstanceOffset,
882 visibleCount = (uint)visibleInstanceCount,
883 batchID = batchIDs[drawBatch.key.overridenComponents],
884 materialID = drawBatch.key.materialID,
885 splitVisibilityMask = (ushort)visibilityMask,
886 lightmapIndex = (ushort)drawBatch.key.lightmapIndex,
887 sortingPosition = sortingPosition,
888 meshID = drawBatch.key.meshID,
889 submeshIndex = (ushort)drawBatch.key.submeshIndex,
890 };
891 }
892 }
893
894 // write the visible instances
895 var instanceOffset = drawBatch.instanceOffset;
896 var instanceCount = drawBatch.instanceCount;
897 var lastRendererIndex = 0;
898 if (binCount > 1)
899 {
900 for (int i = 0; i < instanceCount; ++i)
901 {
902 var rendererIndex = drawInstanceIndices[instanceOffset + i];
903
904 bool isFlipped = IsInstanceFlipped(rendererIndex);
905 int visibilityMask = (int)rendererVisibilityMasks[rendererIndex];
906 if (visibilityMask == 0)
907 continue;
908
909 lastRendererIndex = rendererIndex;
910
911 // add to the instance list for this bin
912 int configIndex = (int)(visibilityMask << 1) | (isFlipped ? 1 : 0);
913 Assert.IsTrue(configIndex < binningConfig.visibilityConfigCount);
914 var visibleInstanceOffset = instanceOffsetPerConfig[configIndex];
915 instanceOffsetPerConfig[configIndex]++;
916
917 if (isIndirect)
918 {
919#if DEBUG
920 if (visibleInstanceOffset >= indirectAllocInfo.instanceCount)
921 throw new Exception("Exceeding visible instance count");
922#endif
923
924 // remove extra bits so that the visibility mask is just the view mask
925 if (binningConfig.supportsCrossFade)
926 visibilityMask >>= 1;
927 if (binningConfig.supportsMotionCheck)
928 visibilityMask >>= 1;
929
930 indirectInstanceInfoGlobalArray[indirectAllocInfo.instanceAllocIndex + visibleInstanceOffset] = new IndirectInstanceInfo
931 {
932 drawOffsetAndSplitMask = (drawCommandOffsetPerConfig[configIndex] << 8) | visibilityMask,
933 instanceIndexAndCrossFade = EncodeGPUInstanceIndexAndCrossFade(rendererIndex, false),
934 };
935 }
936 else
937 {
938#if DEBUG
939 if (visibleInstanceOffset >= output.visibleInstanceCount)
940 throw new Exception("Exceeding visible instance count");
941#endif
942 output.visibleInstances[visibleInstanceOffset] = EncodeGPUInstanceIndexAndCrossFade(rendererIndex, false);
943 }
944 }
945 }
946 else
947 {
948 int visibleInstanceOffset = lastBinInstanceOffset;
949 for (int i = 0; i < instanceCount; ++i)
950 {
951 var rendererIndex = drawInstanceIndices[instanceOffset + i];
952 int visibilityMask = (int)rendererVisibilityMasks[rendererIndex];
953
954 bool isVisible = (visibilityMask != 0);
955 if (!isVisible)
956 continue;
957
958 lastRendererIndex = rendererIndex;
959 if (isIndirect)
960 {
961 // remove extra bits so that the visibility mask is just the view mask
962 if (binningConfig.supportsCrossFade)
963 visibilityMask >>= 1;
964 if (binningConfig.supportsMotionCheck)
965 visibilityMask >>= 1;
966
967 indirectInstanceInfoGlobalArray[indirectAllocInfo.instanceAllocIndex + visibleInstanceOffset] = new IndirectInstanceInfo
968 {
969 drawOffsetAndSplitMask = (batchDrawCommandOffset << 8) | visibilityMask,
970 instanceIndexAndCrossFade = EncodeGPUInstanceIndexAndCrossFade(rendererIndex, false),
971 };
972 }
973 else
974 {
975 output.visibleInstances[visibleInstanceOffset] = EncodeGPUInstanceIndexAndCrossFade(rendererIndex, false);
976 }
977 visibleInstanceOffset++;
978 }
979 }
980
981 // use the first instance position of each batch as the sorting position if necessary
982 if ((drawBatch.key.flags & BatchDrawCommandFlags.HasSortingPosition) != 0)
983 {
984 InstanceHandle instance = InstanceHandle.FromInt(lastRendererIndex & 0xffffff);
985 int instanceIndex = instanceData.InstanceToIndex(instance);
986
987 ref readonly AABB worldAABB = ref instanceData.worldAABBs.UnsafeElementAt(instanceIndex);
988 float3 position = worldAABB.center;
989
990 int globalCommandOffset = batchDrawCommandOffset;
991 if (isIndirect)
992 globalCommandOffset += output.drawCommandCount; // skip over direct commands
993 int sortingPosition = 3 * globalCommandOffset;
994
995 output.instanceSortingPositions[sortingPosition + 0] = position.x;
996 output.instanceSortingPositions[sortingPosition + 1] = position.y;
997 output.instanceSortingPositions[sortingPosition + 2] = position.z;
998 }
999 }
1000 }
1001
1002 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
1003 internal unsafe struct CompactVisibilityMasksJob : IJobParallelForBatch
1004 {
1005 public const int k_BatchSize = 64;
1006
1007 [ReadOnly] public NativeArray<byte> rendererVisibilityMasks;
1008
1009 [NativeDisableContainerSafetyRestriction, NoAlias] public ParallelBitArray compactedVisibilityMasks;
1010
1011 unsafe public void Execute(int startIndex, int count)
1012 {
1013 ulong chunkBits = 0;
1014
1015 for(int i = 0; i < count; ++i)
1016 {
1017 var visibilityMask = rendererVisibilityMasks[startIndex + i];
1018
1019 if(visibilityMask != 0)
1020 chunkBits |= (1ul << i);
1021 }
1022
1023 var chunkIndex = startIndex / k_BatchSize;
1024 compactedVisibilityMasks.InterlockedOrChunk(chunkIndex, chunkBits);
1025 }
1026 }
1027
1028#if UNITY_EDITOR
1029 internal enum FilteringJobMode
1030 {
1031 Filtering,
1032 Picking
1033 }
1034
1035 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
1036 internal unsafe struct DrawCommandOutputFiltering : IJob
1037 {
1038 [ReadOnly] public NativeParallelHashMap<uint, BatchID> batchIDs;
1039 [ReadOnly] public int viewID;
1040
1041 [ReadOnly] public GPUInstanceDataBuffer.ReadOnly instanceDataBuffer;
1042
1043 [ReadOnly] public NativeArray<byte> rendererVisibilityMasks;
1044 [ReadOnly] public NativeArray<byte> rendererCrossFadeValues;
1045
1046 [ReadOnly] public CPUInstanceData.ReadOnly instanceData;
1047 [ReadOnly] public CPUSharedInstanceData.ReadOnly sharedInstanceData;
1048
1049 [ReadOnly] public NativeArray<int> drawInstanceIndices;
1050 [ReadOnly] public NativeList<DrawBatch> drawBatches;
1051 [ReadOnly] public NativeList<DrawRange> drawRanges;
1052 [ReadOnly] public NativeArray<int> drawBatchIndices;
1053
1054 [ReadOnly] public NativeArray<bool> filteringResults;
1055 [ReadOnly] public NativeArray<int> excludedRenderers;
1056
1057 [ReadOnly] public FilteringJobMode mode;
1058
1059 [NativeDisableUnsafePtrRestriction] public NativeArray<BatchCullingOutputDrawCommands> cullingOutput;
1060
1061#if DEBUG
1062 [IgnoreWarning(1370)] //Ignore throwing exception warning.
1063#endif
1064 public void Execute()
1065 {
1066 BatchCullingOutputDrawCommands output = cullingOutput[0];
1067
1068 int maxVisibleInstanceCount = 0;
1069 for (int i = 0; i < drawInstanceIndices.Length; ++i)
1070 {
1071 var rendererIndex = drawInstanceIndices[i];
1072 if (rendererVisibilityMasks[rendererIndex] != 0)
1073 ++maxVisibleInstanceCount;
1074 }
1075 output.visibleInstanceCount = maxVisibleInstanceCount;
1076 output.visibleInstances = MemoryUtilities.Malloc<int>(output.visibleInstanceCount, Allocator.TempJob);
1077
1078 output.drawCommandCount = output.visibleInstanceCount; // for picking/filtering, 1 draw command per instance!
1079 output.drawCommands = MemoryUtilities.Malloc<BatchDrawCommand>(output.drawCommandCount, Allocator.TempJob);
1080 output.drawCommandPickingInstanceIDs = MemoryUtilities.Malloc<int>(output.drawCommandCount, Allocator.TempJob);
1081
1082 int outRangeIndex = 0;
1083 int outCommandIndex = 0;
1084 int outVisibleInstanceIndex = 0;
1085
1086 for (int rangeIndex = 0; rangeIndex < drawRanges.Length; ++rangeIndex)
1087 {
1088 int rangeDrawCommandOffset = outCommandIndex;
1089
1090 var drawRangeInfo = drawRanges[rangeIndex];
1091 for (int drawIndexInRange = 0; drawIndexInRange < drawRangeInfo.drawCount; ++drawIndexInRange)
1092 {
1093 var batchIndex = drawBatchIndices[drawRangeInfo.drawOffset + drawIndexInRange];
1094 DrawBatch drawBatch = drawBatches[batchIndex];
1095 var instanceOffset = drawBatch.instanceOffset;
1096 var instanceCount = drawBatch.instanceCount;
1097
1098 // Output visible instances to the array
1099 for (int i = 0; i < instanceCount; ++i)
1100 {
1101 var rendererIndex = drawInstanceIndices[instanceOffset + i];
1102 var visibilityMask = rendererVisibilityMasks[rendererIndex];
1103 if (visibilityMask == 0)
1104 continue;
1105
1106 InstanceHandle instance = InstanceHandle.FromInt(rendererIndex);
1107 int sharedInstanceIndex = sharedInstanceData.InstanceToIndex(instanceData, instance);
1108
1109 if (mode == FilteringJobMode.Filtering && filteringResults.IsCreated && (sharedInstanceIndex >= filteringResults.Length || !filteringResults[sharedInstanceIndex]))
1110 continue;
1111
1112 var rendererID = sharedInstanceData.rendererGroupIDs[sharedInstanceIndex];
1113 if (mode == FilteringJobMode.Picking && excludedRenderers.IsCreated && excludedRenderers.Contains(rendererID))
1114 continue;
1115
1116#if DEBUG
1117 if (outVisibleInstanceIndex >= output.visibleInstanceCount)
1118 throw new Exception("Exceeding visible instance count");
1119
1120 if (outCommandIndex >= output.drawCommandCount)
1121 throw new Exception("Exceeding draw command count");
1122
1123 if (!batchIDs.ContainsKey(drawBatch.key.overridenComponents))
1124 throw new Exception("Draw command created with an invalid BatchID");
1125#endif
1126 output.visibleInstances[outVisibleInstanceIndex] = instanceDataBuffer.CPUInstanceToGPUInstance(instance).index;
1127 output.drawCommandPickingInstanceIDs[outCommandIndex] = rendererID;
1128 output.drawCommands[outCommandIndex] = new BatchDrawCommand
1129 {
1130 flags = BatchDrawCommandFlags.None,
1131 visibleOffset = (uint)outVisibleInstanceIndex,
1132 visibleCount = (uint)1,
1133 batchID = batchIDs[drawBatch.key.overridenComponents],
1134 materialID = drawBatch.key.materialID,
1135 splitVisibilityMask = 0x1,
1136 lightmapIndex = (ushort)drawBatch.key.lightmapIndex,
1137 sortingPosition = 0,
1138 meshID = drawBatch.key.meshID,
1139 submeshIndex = (ushort)drawBatch.key.submeshIndex,
1140 };
1141
1142 outVisibleInstanceIndex++;
1143 outCommandIndex++;
1144 }
1145 }
1146
1147 // Emit a DrawRange to the array if we have any visible DrawCommands
1148 var rangeDrawCommandCount = outCommandIndex - rangeDrawCommandOffset;
1149 if (rangeDrawCommandCount > 0)
1150 {
1151#if DEBUG
1152 if (outRangeIndex >= output.drawRangeCount)
1153 throw new Exception("Exceeding draw range count");
1154#endif
1155
1156 var rangeKey = drawRangeInfo.key;
1157 output.drawRanges[outRangeIndex] = new BatchDrawRange
1158 {
1159 drawCommandsBegin = (uint)rangeDrawCommandOffset,
1160 drawCommandsCount = (uint)rangeDrawCommandCount,
1161 filterSettings = new BatchFilterSettings
1162 {
1163 renderingLayerMask = rangeKey.renderingLayerMask,
1164 rendererPriority = rangeKey.rendererPriority,
1165 layer = rangeKey.layer,
1166 batchLayer = BatchLayer.InstanceCullingDirect,
1167 motionMode = rangeKey.motionMode,
1168 shadowCastingMode = rangeKey.shadowCastingMode,
1169 receiveShadows = true,
1170 staticShadowCaster = rangeKey.staticShadowCaster,
1171 allDepthSorted = false,
1172 }
1173 };
1174 outRangeIndex++;
1175 }
1176 }
1177
1178 // trim to the number of written ranges/commands/instances
1179 output.drawRangeCount = outRangeIndex;
1180 output.drawCommandCount = outCommandIndex;
1181 output.visibleInstanceCount = outVisibleInstanceIndex;
1182 cullingOutput[0] = output;
1183 }
1184 }
1185
1186 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
1187 internal struct CullSceneViewHiddenRenderersJob : IJobParallelFor
1188 {
1189 public const int k_BatchSize = 128;
1190
1191 [ReadOnly] public CPUInstanceData.ReadOnly instanceData;
1192 [ReadOnly] public CPUSharedInstanceData.ReadOnly sharedInstanceData;
1193 [ReadOnly] public ParallelBitArray hiddenBits;
1194
1195 [NativeDisableParallelForRestriction] public NativeArray<byte> rendererVisibilityMasks;
1196
1197 public void Execute(int instanceIndex)
1198 {
1199 InstanceHandle instance = instanceData.instances[instanceIndex];
1200
1201 if (rendererVisibilityMasks[instance.index] > 0)
1202 {
1203 int sharedInstanceIndex = sharedInstanceData.InstanceToIndex(instanceData, instance);
1204
1205 if (hiddenBits.Get(sharedInstanceIndex))
1206 rendererVisibilityMasks[instance.index] = 0;
1207 }
1208 }
1209 }
1210#endif
1211
1212 internal enum InstanceCullerSplitDebugCounter
1213 {
1214 VisibleInstances,
1215 DrawCommands,
1216 Count,
1217 }
1218
1219 internal struct InstanceCullerSplitDebugArray : IDisposable
1220 {
1221 private const int MaxSplitCount = 64;
1222
1223 internal struct Info
1224 {
1225 public BatchCullingViewType viewType;
1226 public int viewInstanceID;
1227 public int splitIndex;
1228 }
1229
1230 private NativeList<Info> m_Info;
1231 private NativeArray<int> m_Counters;
1232 private NativeQueue<JobHandle> m_CounterSync;
1233
1234 public NativeArray<int> Counters { get => m_Counters; }
1235
1236 public void Init()
1237 {
1238 m_Info = new NativeList<Info>(Allocator.Persistent);
1239 m_Counters = new NativeArray<int>(MaxSplitCount * (int)InstanceCullerSplitDebugCounter.Count, Allocator.Persistent);
1240 m_CounterSync = new NativeQueue<JobHandle>(Allocator.Persistent);
1241 }
1242
1243 public void Dispose()
1244 {
1245 m_Info.Dispose();
1246 m_Counters.Dispose();
1247 m_CounterSync.Dispose();
1248 }
1249
1250 public int TryAddSplits(BatchCullingViewType viewType, int viewInstanceID, int splitCount)
1251 {
1252 int baseIndex = m_Info.Length;
1253 if (baseIndex + splitCount > MaxSplitCount)
1254 return -1;
1255
1256 for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
1257 {
1258 m_Info.Add(new Info()
1259 {
1260 viewType = viewType,
1261 viewInstanceID = viewInstanceID,
1262 splitIndex = splitIndex,
1263 });
1264 }
1265 return baseIndex;
1266 }
1267
1268 public void AddSync(int baseIndex, JobHandle jobHandle)
1269 {
1270 if (baseIndex != -1)
1271 m_CounterSync.Enqueue(jobHandle);
1272 }
1273
1274 public void MoveToDebugStatsAndClear(DebugRendererBatcherStats debugStats)
1275 {
1276 // wait for stats-writing jobs to complete
1277 while (m_CounterSync.TryDequeue(out var jobHandle))
1278 {
1279 jobHandle.Complete();
1280 }
1281
1282 // overwrite debug stats with the latest
1283 debugStats.instanceCullerStats.Clear();
1284 for (int index = 0; index < m_Info.Length; ++index)
1285 {
1286 var info = m_Info[index];
1287 int counterBase = index * (int)InstanceCullerSplitDebugCounter.Count;
1288 debugStats.instanceCullerStats.Add(new InstanceCullerViewStats
1289 {
1290 viewType = info.viewType,
1291 viewInstanceID = info.viewInstanceID,
1292 splitIndex = info.splitIndex,
1293 visibleInstances = m_Counters[counterBase + (int)InstanceCullerSplitDebugCounter.VisibleInstances],
1294 drawCommands = m_Counters[counterBase + (int)InstanceCullerSplitDebugCounter.DrawCommands],
1295 });
1296 }
1297
1298 // clear for next frame
1299 m_Info.Clear();
1300 m_Counters.FillArray(0);
1301 }
1302 }
1303
1304 internal struct InstanceOcclusionEventDebugArray : IDisposable
1305 {
1306 private const int InitialPassCount = 4;
1307 private const int MaxPassCount = 64;
1308
1309 internal struct Info
1310 {
1311 public int viewInstanceID;
1312 public InstanceOcclusionEventType eventType;
1313 public int occluderVersion;
1314 public int subviewMask;
1315 public OcclusionTest occlusionTest;
1316
1317 public bool HasVersion()
1318 {
1319 return eventType == InstanceOcclusionEventType.OccluderUpdate || occlusionTest != OcclusionTest.None;
1320 }
1321 }
1322
1323 internal struct Request
1324 {
1325 public UnsafeList<Info> info;
1326 public AsyncGPUReadbackRequest readback;
1327 }
1328
1329 private GraphicsBuffer m_CounterBuffer;
1330
1331 private UnsafeList<Info> m_PendingInfo;
1332 private NativeQueue<Request> m_Requests;
1333
1334 private UnsafeList<Info> m_LatestInfo;
1335 private NativeArray<int> m_LatestCounters;
1336 private bool m_HasLatest;
1337
1338 public GraphicsBuffer CounterBuffer { get => m_CounterBuffer; }
1339
1340 public void Init()
1341 {
1342 m_CounterBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, MaxPassCount * (int)InstanceOcclusionTestDebugCounter.Count, sizeof(uint));
1343 m_PendingInfo = new UnsafeList<Info>(InitialPassCount, Allocator.Persistent);
1344 m_Requests = new NativeQueue<Request>(Allocator.Persistent);
1345 }
1346
1347 public void Dispose()
1348 {
1349 if (m_HasLatest)
1350 {
1351 m_LatestInfo.Dispose();
1352 m_LatestCounters.Dispose();
1353 m_HasLatest = false;
1354 }
1355 while (m_Requests.TryDequeue(out var req))
1356 {
1357 req.readback.WaitForCompletion();
1358 req.info.Dispose();
1359 }
1360 m_Requests.Dispose();
1361 m_PendingInfo.Dispose();
1362 m_CounterBuffer.Dispose();
1363 }
1364
1365 public int TryAdd(int viewInstanceID, InstanceOcclusionEventType eventType, int occluderVersion, int subviewMask, OcclusionTest occlusionTest)
1366 {
1367 int passIndex = m_PendingInfo.Length;
1368 if (passIndex + 1 > MaxPassCount)
1369 return -1;
1370
1371 m_PendingInfo.Add(new Info()
1372 {
1373 viewInstanceID = viewInstanceID,
1374 eventType = eventType,
1375 occluderVersion = occluderVersion,
1376 subviewMask = subviewMask,
1377 occlusionTest = occlusionTest,
1378 });
1379 return passIndex;
1380 }
1381
1382 public void MoveToDebugStatsAndClear(DebugRendererBatcherStats debugStats)
1383 {
1384 // commit the pending set of stats
1385 if (m_PendingInfo.Length > 0)
1386 {
1387 m_Requests.Enqueue(new Request
1388 {
1389 info = m_PendingInfo,
1390 readback = AsyncGPUReadback.Request(m_CounterBuffer, m_PendingInfo.Length * (int)InstanceOcclusionTestDebugCounter.Count * sizeof(uint), 0)
1391 });
1392 m_PendingInfo = new UnsafeList<Info>(InitialPassCount, Allocator.Persistent);
1393 }
1394
1395 // update the latest set of results that are ready
1396 while (!m_Requests.IsEmpty() && m_Requests.Peek().readback.done)
1397 {
1398 var req = m_Requests.Dequeue();
1399 if (!req.readback.hasError)
1400 {
1401 NativeArray<int> src = req.readback.GetData<int>(0);
1402 if (src.Length == req.info.Length * (int)InstanceOcclusionTestDebugCounter.Count)
1403 {
1404 if (m_HasLatest)
1405 {
1406 m_LatestInfo.Dispose();
1407 m_LatestCounters.Dispose();
1408 m_HasLatest = false;
1409 }
1410 m_LatestInfo = req.info;
1411 m_LatestCounters = new NativeArray<int>(src, Allocator.Persistent);
1412 m_HasLatest = true;
1413 }
1414 }
1415 }
1416
1417 // overwrite debug stats with the latest
1418 debugStats.instanceOcclusionEventStats.Clear();
1419 if (m_HasLatest)
1420 {
1421 for (int index = 0; index < m_LatestInfo.Length; ++index)
1422 {
1423 var info = m_LatestInfo[index];
1424
1425 // make occluder version relative to the first one this frame
1426 int occluderVersion = -1;
1427 if (info.HasVersion())
1428 {
1429 occluderVersion = 0;
1430 for (int prevIndex = 0; prevIndex < index; ++prevIndex)
1431 {
1432 var prevInfo = m_LatestInfo[prevIndex];
1433 if (prevInfo.HasVersion() && prevInfo.viewInstanceID == info.viewInstanceID)
1434 {
1435 occluderVersion = info.occluderVersion - prevInfo.occluderVersion;
1436 break;
1437 }
1438 }
1439 }
1440
1441 int counterBase = index * (int)InstanceOcclusionTestDebugCounter.Count;
1442 int occludedCounter = m_LatestCounters[counterBase + (int)InstanceOcclusionTestDebugCounter.Occluded];
1443 int notOccludedCounter = m_LatestCounters[counterBase + (int)InstanceOcclusionTestDebugCounter.NotOccluded];
1444
1445 debugStats.instanceOcclusionEventStats.Add(new InstanceOcclusionEventStats
1446 {
1447 viewInstanceID = info.viewInstanceID,
1448 eventType = info.eventType,
1449 occluderVersion = occluderVersion,
1450 subviewMask = info.subviewMask,
1451 occlusionTest = info.occlusionTest,
1452 visibleInstances = notOccludedCounter,
1453 culledInstances = occludedCounter,
1454 });
1455 }
1456 }
1457
1458 // clear the GPU buffer for the next frame
1459 var zeros = new NativeArray<int>(MaxPassCount * (int)InstanceOcclusionTestDebugCounter.Count, Allocator.Temp, NativeArrayOptions.ClearMemory);
1460 m_CounterBuffer.SetData(zeros);
1461 zeros.Dispose();
1462 }
1463 }
1464
1465 internal struct InstanceCuller : IDisposable
1466 {
1467 //@ Move this in CPUInstanceData.
1468 private ParallelBitArray m_CompactedVisibilityMasks;
1469 private JobHandle m_CompactedVisibilityMasksJobsHandle;
1470
1471 private IndirectBufferContextStorage m_IndirectStorage;
1472
1473 private OcclusionTestComputeShader m_OcclusionTestShader;
1474 private int m_ResetDrawArgsKernel;
1475 private int m_CopyInstancesKernel;
1476 private int m_CullInstancesKernel;
1477
1478 private DebugRendererBatcherStats m_DebugStats;
1479 private InstanceCullerSplitDebugArray m_SplitDebugArray;
1480 private InstanceOcclusionEventDebugArray m_OcclusionEventDebugArray;
1481 private ProfilingSampler m_ProfilingSampleInstanceOcclusionTest;
1482
1483 private NativeArray<InstanceOcclusionCullerShaderVariables> m_ShaderVariables;
1484 private ComputeBuffer m_ConstantBuffer;
1485
1486 private CommandBuffer m_CommandBuffer;
1487
1488#if UNITY_EDITOR
1489 private bool m_IsSceneViewCamera;
1490 private ParallelBitArray m_SceneViewHiddenBits;
1491#endif
1492
1493 private static class ShaderIDs
1494 {
1495 public static readonly int InstanceOcclusionCullerShaderVariables = Shader.PropertyToID("InstanceOcclusionCullerShaderVariables");
1496 public static readonly int _DrawInfo = Shader.PropertyToID("_DrawInfo");
1497 public static readonly int _InstanceInfo = Shader.PropertyToID("_InstanceInfo");
1498 public static readonly int _DrawArgs = Shader.PropertyToID("_DrawArgs");
1499 public static readonly int _InstanceIndices = Shader.PropertyToID("_InstanceIndices");
1500 public static readonly int _InstanceDataBuffer = Shader.PropertyToID("_InstanceDataBuffer");
1501
1502 // Debug
1503 public static readonly int _OccluderDepthPyramid = Shader.PropertyToID("_OccluderDepthPyramid");
1504 public static readonly int _OcclusionDebugCounters = Shader.PropertyToID("_OcclusionDebugCounters");
1505 }
1506
1507 internal void Init(GPUResidentDrawerResources resources, DebugRendererBatcherStats debugStats = null)
1508 {
1509 m_IndirectStorage.Init();
1510
1511 m_OcclusionTestShader.Init(resources.instanceOcclusionCullingKernels);
1512 m_ResetDrawArgsKernel = m_OcclusionTestShader.cs.FindKernel("ResetDrawArgs");
1513 m_CopyInstancesKernel = m_OcclusionTestShader.cs.FindKernel("CopyInstances");
1514 m_CullInstancesKernel = m_OcclusionTestShader.cs.FindKernel("CullInstances");
1515
1516 m_DebugStats = debugStats;
1517 m_SplitDebugArray = new InstanceCullerSplitDebugArray();
1518 m_SplitDebugArray.Init();
1519 m_OcclusionEventDebugArray = new InstanceOcclusionEventDebugArray();
1520 m_OcclusionEventDebugArray.Init();
1521
1522 m_ProfilingSampleInstanceOcclusionTest = new ProfilingSampler("InstanceOcclusionTest");
1523
1524 m_ShaderVariables = new NativeArray<InstanceOcclusionCullerShaderVariables>(1, Allocator.Persistent);
1525 m_ConstantBuffer = new ComputeBuffer(1, UnsafeUtility.SizeOf<InstanceOcclusionCullerShaderVariables>(), ComputeBufferType.Constant);
1526
1527 m_CommandBuffer = new CommandBuffer();
1528 m_CommandBuffer.name = "EnsureValidOcclusionTestResults";
1529 }
1530
1531 [BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
1532 private unsafe struct SetupCullingJobInput : IJob
1533 {
1534 public float lodBias;
1535 [NativeDisableUnsafePtrRestriction] public BatchCullingContext* context;
1536 [NativeDisableUnsafePtrRestriction] public ReceiverPlanes* receiverPlanes;
1537 [NativeDisableUnsafePtrRestriction] public ReceiverSphereCuller* receiverSphereCuller;
1538 [NativeDisableUnsafePtrRestriction] public FrustumPlaneCuller* frustumPlaneCuller;
1539 [NativeDisableUnsafePtrRestriction] public float* screenRelativeMetric;
1540
1541 public void Execute()
1542 {
1543 *receiverPlanes = ReceiverPlanes.Create(*context, Allocator.TempJob);
1544 *receiverSphereCuller = ReceiverSphereCuller.Create(*context, Allocator.TempJob);
1545 *frustumPlaneCuller = FrustumPlaneCuller.Create(*context, receiverPlanes->planes.AsArray(), *receiverSphereCuller, Allocator.TempJob);
1546 *screenRelativeMetric = LODGroupRenderingUtils.CalculateScreenRelativeMetric(context->lodParameters, lodBias);
1547 }
1548 }
1549
1550 private unsafe JobHandle CreateFrustumCullingJob(
1551 in BatchCullingContext cc,
1552 in CPUInstanceData.ReadOnly instanceData,
1553 in CPUSharedInstanceData.ReadOnly sharedInstanceData,
1554 NativeList<LODGroupCullingData> lodGroupCullingData,
1555 in BinningConfig binningConfig,
1556 float smallMeshScreenPercentage,
1557 OcclusionCullingCommon occlusionCullingCommon,
1558 NativeArray<byte> rendererVisibilityMasks,
1559 NativeArray<byte> rendererCrossFadeValues)
1560 {
1561 Assert.IsTrue(cc.cullingSplits.Length <= 6, "InstanceCullingBatcher supports up to 6 culling splits.");
1562
1563 ReceiverPlanes receiverPlanes;
1564 ReceiverSphereCuller receiverSphereCuller;
1565 FrustumPlaneCuller frustumPlaneCuller;
1566 float screenRelativeMetric;
1567
1568 fixed (BatchCullingContext* contextPtr = &cc)
1569 {
1570 new SetupCullingJobInput()
1571 {
1572 lodBias = QualitySettings.lodBias,
1573 context = contextPtr,
1574 frustumPlaneCuller = &frustumPlaneCuller,
1575 receiverPlanes = &receiverPlanes,
1576 receiverSphereCuller = &receiverSphereCuller,
1577 screenRelativeMetric = &screenRelativeMetric,
1578
1579 }.Run();
1580 }
1581
1582 if (occlusionCullingCommon != null)
1583 occlusionCullingCommon.UpdateSilhouettePlanes(cc.viewID.GetInstanceID(), receiverPlanes.SilhouettePlaneSubArray());
1584
1585 var cullingJob = new CullingJob
1586 {
1587 binningConfig = binningConfig,
1588 viewType = cc.viewType,
1589 frustumPlanePackets = frustumPlaneCuller.planePackets.AsArray(),
1590 frustumSplitInfos = frustumPlaneCuller.splitInfos.AsArray(),
1591 lightFacingFrustumPlanes = receiverPlanes.LightFacingFrustumPlaneSubArray(),
1592 receiverSplitInfos = receiverSphereCuller.splitInfos.AsArray(),
1593 worldToLightSpaceRotation = receiverSphereCuller.worldToLightSpaceRotation,
1594 cullLightmappedShadowCasters = (cc.cullingFlags & BatchCullingFlags.CullLightmappedShadowCasters) != 0,
1595 cameraPosition = cc.lodParameters.cameraPosition,
1596 sqrScreenRelativeMetric = screenRelativeMetric * screenRelativeMetric,
1597 minScreenRelativeHeight = smallMeshScreenPercentage * 0.01f,
1598 isOrtho = cc.lodParameters.isOrthographic,
1599 instanceData = instanceData,
1600 sharedInstanceData = sharedInstanceData,
1601 lodGroupCullingData = lodGroupCullingData,
1602 occlusionBuffer = cc.occlusionBuffer,
1603 rendererVisibilityMasks = rendererVisibilityMasks,
1604 rendererCrossFadeValues = rendererCrossFadeValues,
1605 maxLOD = QualitySettings.maximumLODLevel,
1606 cullingLayerMask = cc.cullingLayerMask,
1607 sceneCullingMask = cc.sceneCullingMask,
1608
1609 }.Schedule(instanceData.instancesLength, CullingJob.k_BatchSize);
1610
1611 receiverPlanes.Dispose(cullingJob);
1612 frustumPlaneCuller.Dispose(cullingJob);
1613 receiverSphereCuller.Dispose(cullingJob);
1614
1615 return cullingJob;
1616 }
1617
1618 private int ComputeWorstCaseDrawCommandCount(
1619 in BatchCullingContext cc,
1620 BinningConfig binningConfig,
1621 CPUDrawInstanceData drawInstanceData,
1622 int crossFadedRendererCount)
1623 {
1624 int visibleInstancesCount = drawInstanceData.drawInstances.Length;
1625 int drawCommandCount = drawInstanceData.drawBatches.Length;
1626
1627 // add the number of batches split due to actively cross-fading
1628 drawCommandCount += math.min(crossFadedRendererCount, drawCommandCount);
1629
1630 // batches can be split due to flip winding
1631 drawCommandCount *= 2;
1632
1633 // and actively moving
1634 if (binningConfig.supportsMotionCheck)
1635 drawCommandCount *= 2;
1636
1637 if (cc.cullingSplits.Length > 1)
1638 {
1639 // visible instances are only written once, grouped by visibility mask bit pattern
1640 // draw calls are split for each unique visibility mask bit pattern
1641 // handle the worst case where each draw has an instance for every possible mask
1642 drawCommandCount <<= (cc.cullingSplits.Length - 1);
1643 }
1644
1645 // empty draw commands are skipped, so there cannot be more draw commands than visible instances
1646 drawCommandCount = math.min(drawCommandCount, visibleInstancesCount);
1647
1648 return drawCommandCount;
1649 }
1650
1651 public unsafe JobHandle CreateCullJobTree(
1652 in BatchCullingContext cc,
1653 BatchCullingOutput cullingOutput,
1654 in CPUInstanceData.ReadOnly instanceData,
1655 in CPUSharedInstanceData.ReadOnly sharedInstanceData,
1656 in GPUInstanceDataBuffer.ReadOnly instanceDataBuffer,
1657 NativeList<LODGroupCullingData> lodGroupCullingData,
1658 CPUDrawInstanceData drawInstanceData,
1659 NativeParallelHashMap<uint, BatchID> batchIDs,
1660 int crossFadedRendererCount,
1661 float smallMeshScreenPercentage,
1662 OcclusionCullingCommon occlusionCullingCommon)
1663 {
1664 // allocate for worst case number of draw ranges (all other arrays allocated after size is known)
1665 var drawCommands = new BatchCullingOutputDrawCommands();
1666 drawCommands.drawRangeCount = drawInstanceData.drawRanges.Length;
1667 drawCommands.drawRanges = MemoryUtilities.Malloc<BatchDrawRange>(drawCommands.drawRangeCount, Allocator.TempJob);
1668 for (int i = 0; i < drawCommands.drawRangeCount; ++i)
1669 drawCommands.drawRanges[i].drawCommandsCount = 0;
1670 cullingOutput.drawCommands[0] = drawCommands;
1671 cullingOutput.customCullingResult[0] = IntPtr.Zero;
1672
1673 var binningConfig = new BinningConfig
1674 {
1675 viewCount = cc.cullingSplits.Length,
1676 supportsCrossFade = (crossFadedRendererCount > 0),
1677 supportsMotionCheck = (cc.viewType == BatchCullingViewType.Camera), // TODO: could disable here if RP never needs object motion vectors, for now always batch on it
1678 };
1679
1680 var visibilityLength = instanceData.handlesLength;
1681 var rendererVisibilityMasks = new NativeArray<byte>(visibilityLength, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1682 var rendererCrossFadeValues = new NativeArray<byte>(visibilityLength, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1683
1684 var cullingJobHandle = CreateFrustumCullingJob(cc, instanceData, sharedInstanceData, lodGroupCullingData, binningConfig,
1685 smallMeshScreenPercentage, occlusionCullingCommon, rendererVisibilityMasks, rendererCrossFadeValues);
1686
1687#if UNITY_EDITOR
1688 // Unfortunately BatchCullingContext doesn't provide full visibility and picking context.
1689 // Including which object is hidden in the hierarchy panel or not pickable in the scene view for tooling purposes.
1690 // So we have to manually handle bold editor logic here inside the culler.
1691 // This should be redesigned in the future. Culler should not be responsible for custom editor handling logic or even know that the editor exist.
1692
1693 // This additionally culls game objects hidden in the hierarchy panel or the scene view or in context editing.
1694 cullingJobHandle = CreateSceneViewHiddenObjectsCullingJob_EditorOnly(cc, instanceData, sharedInstanceData, rendererVisibilityMasks,
1695 cullingJobHandle);
1696
1697 if (cc.viewType == BatchCullingViewType.Picking)
1698 {
1699 // This outputs picking draw commands for the objects that can be picked.
1700 cullingJobHandle = CreatePickingCullingOutputJob_EditorOnly(cc, cullingOutput, instanceData, sharedInstanceData, instanceDataBuffer,
1701 drawInstanceData, batchIDs, rendererVisibilityMasks, rendererCrossFadeValues, cullingJobHandle);
1702 }
1703 else if (cc.viewType == BatchCullingViewType.Filtering)
1704 {
1705 // This outputs draw commands for the objects filtered by search input in the hierarchy on in the scene view.
1706 cullingJobHandle = CreateFilteringCullingOutputJob_EditorOnly(cc, cullingOutput, instanceData, sharedInstanceData, instanceDataBuffer, drawInstanceData,
1707 batchIDs, rendererVisibilityMasks, rendererCrossFadeValues, cullingJobHandle);
1708 }
1709#endif
1710 // This outputs regular draw commands.
1711 if (cc.viewType == BatchCullingViewType.Camera || cc.viewType == BatchCullingViewType.Light || cc.viewType == BatchCullingViewType.SelectionOutline)
1712 {
1713 cullingJobHandle = CreateCompactedVisibilityMaskJob(instanceData, rendererVisibilityMasks, cullingJobHandle);
1714
1715 int debugCounterBaseIndex = -1;
1716 if (m_DebugStats?.enabled ?? false)
1717 {
1718 debugCounterBaseIndex = m_SplitDebugArray.TryAddSplits(cc.viewType, cc.viewID.GetInstanceID(), cc.cullingSplits.Length);
1719 }
1720
1721 var batchCount = drawInstanceData.drawBatches.Length;
1722 int maxBinCount = ComputeWorstCaseDrawCommandCount(cc, binningConfig, drawInstanceData, crossFadedRendererCount);
1723
1724 var batchBinAllocOffsets = new NativeArray<int>(batchCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1725 var batchBinCounts = new NativeArray<int>(batchCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1726 var batchDrawCommandOffsets = new NativeArray<int>(batchCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1727
1728 var binAllocCounter = new NativeArray<int>(JobsUtility.CacheLineSize / sizeof(int), Allocator.TempJob);
1729 var binConfigIndices = new NativeArray<short>(maxBinCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1730 var binVisibleInstanceCounts = new NativeArray<int>(maxBinCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1731 var binVisibleInstanceOffsets = new NativeArray<int>(maxBinCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1732
1733 int indirectContextIndex = -1;
1734 bool useOcclusionCulling = (occlusionCullingCommon != null) && occlusionCullingCommon.HasOccluderContext(cc.viewID.GetInstanceID());
1735 if (useOcclusionCulling)
1736 {
1737 int viewInstanceID = cc.viewID.GetInstanceID();
1738 indirectContextIndex = m_IndirectStorage.TryAllocateContext(viewInstanceID);
1739 cullingOutput.customCullingResult[0] = (IntPtr)viewInstanceID;
1740 }
1741 IndirectBufferLimits indirectBufferLimits = m_IndirectStorage.GetLimits(indirectContextIndex);
1742 NativeArray<IndirectBufferAllocInfo> indirectBufferAllocInfo = m_IndirectStorage.GetAllocInfoSubArray(indirectContextIndex);
1743
1744 var allocateBinsJob = new AllocateBinsPerBatch
1745 {
1746 binningConfig = binningConfig,
1747 drawBatches = drawInstanceData.drawBatches,
1748 drawInstanceIndices = drawInstanceData.drawInstanceIndices,
1749 instanceData = instanceData,
1750 rendererVisibilityMasks = rendererVisibilityMasks,
1751 batchBinAllocOffsets = batchBinAllocOffsets,
1752 batchBinCounts = batchBinCounts,
1753 binAllocCounter = binAllocCounter,
1754 binConfigIndices = binConfigIndices,
1755 binVisibleInstanceCounts = binVisibleInstanceCounts,
1756 splitDebugCounters = m_SplitDebugArray.Counters,
1757 debugCounterIndexBase = debugCounterBaseIndex,
1758 };
1759
1760 var allocateBinsHandle = allocateBinsJob.Schedule(batchCount, 1, cullingJobHandle);
1761
1762 m_SplitDebugArray.AddSync(debugCounterBaseIndex, allocateBinsHandle);
1763
1764 var prefixSumJob = new PrefixSumDrawsAndInstances
1765 {
1766 drawRanges = drawInstanceData.drawRanges,
1767 drawBatchIndices = drawInstanceData.drawBatchIndices,
1768 batchBinAllocOffsets = batchBinAllocOffsets,
1769 batchBinCounts = batchBinCounts,
1770 binVisibleInstanceCounts = binVisibleInstanceCounts,
1771 batchDrawCommandOffsets = batchDrawCommandOffsets,
1772 binVisibleInstanceOffsets = binVisibleInstanceOffsets,
1773 cullingOutput = cullingOutput.drawCommands,
1774 indirectBufferLimits = indirectBufferLimits,
1775 indirectBufferAllocInfo = indirectBufferAllocInfo,
1776 indirectAllocationCounters = m_IndirectStorage.allocationCounters,
1777 };
1778
1779 var prefixSumHandle = prefixSumJob.Schedule(allocateBinsHandle);
1780
1781 var drawCommandOutputJob = new DrawCommandOutputPerBatch
1782 {
1783 binningConfig = binningConfig,
1784 batchIDs = batchIDs,
1785 instanceDataBuffer = instanceDataBuffer,
1786 drawBatches = drawInstanceData.drawBatches,
1787 drawInstanceIndices = drawInstanceData.drawInstanceIndices,
1788 instanceData = instanceData,
1789 rendererVisibilityMasks = rendererVisibilityMasks,
1790 rendererCrossFadeValues = rendererCrossFadeValues,
1791 batchBinAllocOffsets = batchBinAllocOffsets,
1792 batchBinCounts = batchBinCounts,
1793 batchDrawCommandOffsets = batchDrawCommandOffsets,
1794 binConfigIndices = binConfigIndices,
1795 binVisibleInstanceOffsets = binVisibleInstanceOffsets,
1796 binVisibleInstanceCounts = binVisibleInstanceCounts,
1797 cullingOutput = cullingOutput.drawCommands,
1798 indirectBufferLimits = indirectBufferLimits,
1799 visibleInstancesBufferHandle = m_IndirectStorage.visibleInstanceBufferHandle,
1800 indirectArgsBufferHandle = m_IndirectStorage.indirectArgsBufferHandle,
1801 indirectBufferAllocInfo = indirectBufferAllocInfo,
1802 indirectInstanceInfoGlobalArray = m_IndirectStorage.instanceInfoGlobalArray,
1803 indirectDrawInfoGlobalArray = m_IndirectStorage.drawInfoGlobalArray,
1804 };
1805
1806 var drawCommandOutputHandle = drawCommandOutputJob.Schedule(batchCount, 1, prefixSumHandle);
1807
1808 if (useOcclusionCulling)
1809 m_IndirectStorage.SetBufferContext(indirectContextIndex, new IndirectBufferContext(drawCommandOutputHandle));
1810
1811 cullingJobHandle = drawCommandOutputHandle;
1812 }
1813
1814 cullingJobHandle = rendererVisibilityMasks.Dispose(cullingJobHandle);
1815 cullingJobHandle = rendererCrossFadeValues.Dispose(cullingJobHandle);
1816
1817 return cullingJobHandle;
1818 }
1819
1820 private JobHandle CreateCompactedVisibilityMaskJob(in CPUInstanceData.ReadOnly instanceData, NativeArray<byte> rendererVisibilityMasks, JobHandle cullingJobHandle)
1821 {
1822 if (!m_CompactedVisibilityMasks.IsCreated)
1823 {
1824 Assert.IsTrue(m_CompactedVisibilityMasksJobsHandle.IsCompleted);
1825 m_CompactedVisibilityMasks = new ParallelBitArray(instanceData.handlesLength, Allocator.TempJob);
1826 }
1827
1828 var compactVisibilityMasksJob = new CompactVisibilityMasksJob
1829 {
1830 rendererVisibilityMasks = rendererVisibilityMasks,
1831 compactedVisibilityMasks = m_CompactedVisibilityMasks
1832 };
1833
1834 var compactVisibilityMasksJobHandle = compactVisibilityMasksJob.ScheduleBatch(rendererVisibilityMasks.Length, CompactVisibilityMasksJob.k_BatchSize, cullingJobHandle);
1835 m_CompactedVisibilityMasksJobsHandle = JobHandle.CombineDependencies(m_CompactedVisibilityMasksJobsHandle, compactVisibilityMasksJobHandle);
1836
1837 return compactVisibilityMasksJobHandle;
1838 }
1839
1840#if UNITY_EDITOR
1841
1842 private JobHandle CreateSceneViewHiddenObjectsCullingJob_EditorOnly(in BatchCullingContext cc, in CPUInstanceData.ReadOnly instanceData,
1843 in CPUSharedInstanceData.ReadOnly sharedInstanceData, NativeArray<byte> rendererVisibilityMasks, JobHandle cullingJobHandle)
1844 {
1845 bool isSceneViewCamera = m_IsSceneViewCamera && (cc.viewType == BatchCullingViewType.Camera || cc.viewType == BatchCullingViewType.Light);
1846 bool isEditorCullingViewType = cc.viewType == BatchCullingViewType.Picking || cc.viewType == BatchCullingViewType.SelectionOutline
1847 || cc.viewType == BatchCullingViewType.Filtering;
1848
1849 if (!isSceneViewCamera && !isEditorCullingViewType)
1850 return cullingJobHandle;
1851
1852 bool isEditingPrefab = PrefabStageUtility.GetCurrentPrefabStage() != null;
1853 bool isAnyObjectHidden = false;
1854
1855 for (int i = 0; i < SceneManager.sceneCount; ++i)
1856 {
1857 Scene scene = SceneManager.GetSceneAt(i);
1858 if (SceneVisibilityManager.instance.AreAnyDescendantsHidden(scene))
1859 {
1860 isAnyObjectHidden = true;
1861 break;
1862 }
1863 }
1864
1865 if (!isAnyObjectHidden && !isEditingPrefab)
1866 return cullingJobHandle;
1867
1868 int renderersLength = sharedInstanceData.rendererGroupIDs.Length;
1869
1870 if (!m_SceneViewHiddenBits.IsCreated)
1871 {
1872 m_SceneViewHiddenBits = new ParallelBitArray(renderersLength, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
1873 EditorCameraUtils.GetRenderersHiddenResultBits(sharedInstanceData.rendererGroupIDs, m_SceneViewHiddenBits.GetBitsArray().Reinterpret<ulong>());
1874 }
1875
1876 var jobHandle = new CullSceneViewHiddenRenderersJob
1877 {
1878 instanceData = instanceData,
1879 sharedInstanceData = sharedInstanceData,
1880 rendererVisibilityMasks = rendererVisibilityMasks,
1881 hiddenBits = m_SceneViewHiddenBits,
1882 }.Schedule(instanceData.instancesLength, CullSceneViewHiddenRenderersJob.k_BatchSize, cullingJobHandle);
1883
1884 return jobHandle;
1885 }
1886
1887 private JobHandle CreateFilteringCullingOutputJob_EditorOnly(in BatchCullingContext cc, BatchCullingOutput cullingOutput,
1888 in CPUInstanceData.ReadOnly instanceData, in CPUSharedInstanceData.ReadOnly sharedInstanceData, in GPUInstanceDataBuffer.ReadOnly instanceDataBuffer,
1889 in CPUDrawInstanceData drawInstanceData, NativeParallelHashMap<uint, BatchID> batchIDs, NativeArray<byte> rendererVisibilityMasks,
1890 NativeArray<byte> rendererCrossFadeValues, JobHandle cullingJobHandle)
1891 {
1892 NativeArray<bool> filteredRenderers = new NativeArray<bool>(sharedInstanceData.rendererGroupIDs.Length, Allocator.TempJob);
1893 EditorCameraUtils.GetRenderersFilteringResults(sharedInstanceData.rendererGroupIDs, filteredRenderers);
1894 var dummyExcludedRenderers = new NativeArray<int>(0, Allocator.TempJob);
1895
1896 var drawOutputJob = new DrawCommandOutputFiltering
1897 {
1898 viewID = cc.viewID.GetInstanceID(),
1899 batchIDs = batchIDs,
1900 instanceDataBuffer = instanceDataBuffer,
1901 rendererVisibilityMasks = rendererVisibilityMasks,
1902 rendererCrossFadeValues = rendererCrossFadeValues,
1903 instanceData = instanceData,
1904 sharedInstanceData = sharedInstanceData,
1905 drawInstanceIndices = drawInstanceData.drawInstanceIndices,
1906 drawBatches = drawInstanceData.drawBatches,
1907 drawRanges = drawInstanceData.drawRanges,
1908 drawBatchIndices = drawInstanceData.drawBatchIndices,
1909 filteringResults = filteredRenderers,
1910 excludedRenderers = dummyExcludedRenderers,
1911 cullingOutput = cullingOutput.drawCommands,
1912 mode = FilteringJobMode.Filtering
1913 };
1914
1915 var drawOutputHandle = drawOutputJob.Schedule(cullingJobHandle);
1916
1917 filteredRenderers.Dispose(drawOutputHandle);
1918 dummyExcludedRenderers.Dispose(drawOutputHandle);
1919
1920 return drawOutputHandle;
1921 }
1922
1923 private JobHandle CreatePickingCullingOutputJob_EditorOnly(in BatchCullingContext cc, BatchCullingOutput cullingOutput,
1924 in CPUInstanceData.ReadOnly instanceData, in CPUSharedInstanceData.ReadOnly sharedInstanceData, in GPUInstanceDataBuffer.ReadOnly instanceDataBuffer,
1925 in CPUDrawInstanceData drawInstanceData, NativeParallelHashMap<uint, BatchID> batchIDs, NativeArray<byte> rendererVisibilityMasks,
1926 NativeArray<byte> rendererCrossFadeValues, JobHandle cullingJobHandle)
1927 {
1928 // GPUResindetDrawer doesn't handle rendering of persistent game objects like prefabs. They are rendered by SRP.
1929 // When we are in prefab editing mode all the objects that are not part of the prefab should not be pickable.
1930 if (PrefabStageUtility.GetCurrentPrefabStage() != null)
1931 return cullingJobHandle;
1932
1933 var pickingIDs = HandleUtility.GetPickingIncludeExcludeList(Allocator.TempJob);
1934 var excludedRenderers = pickingIDs.ExcludeRenderers.IsCreated ? pickingIDs.ExcludeRenderers : new NativeArray<int>(0, Allocator.TempJob);
1935 var dummyFilteringResults = new NativeArray<bool>(0, Allocator.TempJob);
1936
1937 var drawOutputJob = new DrawCommandOutputFiltering
1938 {
1939 viewID = cc.viewID.GetInstanceID(),
1940 batchIDs = batchIDs,
1941 instanceDataBuffer = instanceDataBuffer,
1942 rendererVisibilityMasks = rendererVisibilityMasks,
1943 rendererCrossFadeValues = rendererCrossFadeValues,
1944 instanceData = instanceData,
1945 sharedInstanceData = sharedInstanceData,
1946 drawInstanceIndices = drawInstanceData.drawInstanceIndices,
1947 drawBatches = drawInstanceData.drawBatches,
1948 drawRanges = drawInstanceData.drawRanges,
1949 drawBatchIndices = drawInstanceData.drawBatchIndices,
1950 filteringResults = dummyFilteringResults,
1951 excludedRenderers = excludedRenderers,
1952 cullingOutput = cullingOutput.drawCommands,
1953 mode = FilteringJobMode.Picking
1954 };
1955
1956 var drawOutputHandle = drawOutputJob.Schedule(cullingJobHandle);
1957 drawOutputHandle.Complete();
1958
1959 dummyFilteringResults.Dispose();
1960 if (!pickingIDs.ExcludeRenderers.IsCreated)
1961 excludedRenderers.Dispose();
1962 pickingIDs.Dispose();
1963
1964 return drawOutputHandle;
1965 }
1966
1967#endif
1968
1969 public void InstanceOccludersUpdated(int viewInstanceID, int subviewMask, RenderersBatchersContext batchersContext)
1970 {
1971 if (m_DebugStats?.enabled ?? false)
1972 {
1973 var occlusionCullingCommon = batchersContext.occlusionCullingCommon;
1974 bool hasOccluders = occlusionCullingCommon.GetOccluderContext(viewInstanceID, out OccluderContext occluderCtx);
1975 if (hasOccluders)
1976 {
1977 m_OcclusionEventDebugArray.TryAdd(
1978 viewInstanceID,
1979 InstanceOcclusionEventType.OccluderUpdate,
1980 occluderCtx.version,
1981 subviewMask,
1982 OcclusionTest.None);
1983 }
1984 }
1985 }
1986
1987 private void DisposeCompactVisibilityMasks()
1988 {
1989 if (m_CompactedVisibilityMasks.IsCreated)
1990 {
1991 Assert.IsTrue(m_CompactedVisibilityMasksJobsHandle.IsCompleted);
1992 m_CompactedVisibilityMasks.Dispose();
1993 }
1994 }
1995
1996 private void DisposeSceneViewHiddenBits()
1997 {
1998#if UNITY_EDITOR
1999 if (m_SceneViewHiddenBits.IsCreated)
2000 m_SceneViewHiddenBits.Dispose();
2001#endif
2002 }
2003
2004 public ParallelBitArray GetCompactedVisibilityMasks(bool syncCullingJobs)
2005 {
2006 if (syncCullingJobs)
2007 m_CompactedVisibilityMasksJobsHandle.Complete();
2008
2009 return m_CompactedVisibilityMasks;
2010 }
2011
2012 private class InstanceOcclusionTestPassData
2013 {
2014 public OcclusionCullingSettings settings;
2015 public InstanceOcclusionTestSubviewSettings subviewSettings;
2016 public OccluderHandles occluderHandles;
2017 public IndirectBufferContextHandles bufferHandles;
2018 }
2019
2020 public void InstanceOcclusionTest(RenderGraph renderGraph, in OcclusionCullingSettings settings, ReadOnlySpan<SubviewOcclusionTest> subviewOcclusionTests, RenderersBatchersContext batchersContext)
2021 {
2022 if (!batchersContext.occlusionCullingCommon.GetOccluderContext(settings.viewInstanceID, out OccluderContext occluderCtx))
2023 return;
2024
2025 var occluderHandles = occluderCtx.Import(renderGraph);
2026 if (!occluderHandles.IsValid())
2027 return;
2028
2029 using (var builder = renderGraph.AddComputePass<InstanceOcclusionTestPassData>("Instance Occlusion Test", out var passData, m_ProfilingSampleInstanceOcclusionTest))
2030 {
2031 builder.AllowGlobalStateModification(true);
2032
2033 passData.settings = settings;
2034 passData.subviewSettings = InstanceOcclusionTestSubviewSettings.FromSpan(subviewOcclusionTests);
2035 passData.bufferHandles = m_IndirectStorage.ImportBuffers(renderGraph);
2036 passData.occluderHandles = occluderHandles;
2037
2038 passData.bufferHandles.UseForOcclusionTest(builder);
2039 passData.occluderHandles.UseForOcclusionTest(builder);
2040
2041 builder.SetRenderFunc((InstanceOcclusionTestPassData data, ComputeGraphContext context) =>
2042 {
2043 var batcher = GPUResidentDrawer.instance.batcher;
2044 batcher.instanceCullingBatcher.culler.AddOcclusionCullingDispatch(
2045 context.cmd,
2046 data.settings,
2047 data.subviewSettings,
2048 data.bufferHandles,
2049 data.occluderHandles,
2050 batcher.batchersContext);
2051 });
2052 }
2053 }
2054
2055 internal void EnsureValidOcclusionTestResults(int viewInstanceID)
2056 {
2057 int indirectContextIndex = m_IndirectStorage.TryGetContextIndex(viewInstanceID);
2058 if (indirectContextIndex >= 0)
2059 {
2060 // sync before checking the allocation results
2061 IndirectBufferContext bufferCtx = m_IndirectStorage.GetBufferContext(indirectContextIndex);
2062 if (bufferCtx.bufferState == IndirectBufferContext.BufferState.Pending)
2063 bufferCtx.cullingJobHandle.Complete();
2064
2065 // if this did allocate, then ensure the indirect args start with valid data that renders everything
2066 IndirectBufferAllocInfo allocInfo = m_IndirectStorage.GetAllocInfo(indirectContextIndex);
2067 if (!allocInfo.IsEmpty())
2068 {
2069 var cmd = m_CommandBuffer;
2070
2071 cmd.Clear();
2072 m_IndirectStorage.CopyFromStaging(cmd, allocInfo);
2073
2074 var cs = m_OcclusionTestShader.cs;
2075
2076 m_ShaderVariables[0] = new InstanceOcclusionCullerShaderVariables
2077 {
2078 _DrawInfoAllocIndex = (uint)allocInfo.drawAllocIndex,
2079 _DrawInfoCount = (uint)allocInfo.drawCount,
2080 _InstanceInfoAllocIndex = (uint)(IndirectBufferContextStorage.kInstanceInfoGpuOffsetMultiplier * allocInfo.instanceAllocIndex),
2081 _InstanceInfoCount = (uint)allocInfo.instanceCount,
2082 _BoundingSphereInstanceDataAddress = 0,
2083 _DebugCounterIndex = -1,
2084 _InstanceMultiplierShift = 0,
2085 };
2086 cmd.SetBufferData(m_ConstantBuffer, m_ShaderVariables);
2087 cmd.SetComputeConstantBufferParam(cs, ShaderIDs.InstanceOcclusionCullerShaderVariables, m_ConstantBuffer, 0, m_ConstantBuffer.stride);
2088
2089 int kernel = m_CopyInstancesKernel;
2090 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawInfo, m_IndirectStorage.drawInfoBuffer);
2091 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceInfo, m_IndirectStorage.instanceInfoBuffer);
2092 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawArgs, m_IndirectStorage.argsBuffer);
2093 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceIndices, m_IndirectStorage.instanceBuffer);
2094
2095 cmd.DispatchCompute(cs, kernel, (allocInfo.instanceCount + 63) / 64, 1, 1);
2096
2097 Graphics.ExecuteCommandBuffer(cmd);
2098 cmd.Clear();
2099 }
2100 }
2101 }
2102
2103 private void AddOcclusionCullingDispatch(
2104 ComputeCommandBuffer cmd,
2105 in OcclusionCullingSettings settings,
2106 in InstanceOcclusionTestSubviewSettings subviewSettings,
2107 in IndirectBufferContextHandles bufferHandles,
2108 in OccluderHandles occluderHandles,
2109 RenderersBatchersContext batchersContext)
2110 {
2111 var occlusionCullingCommon = batchersContext.occlusionCullingCommon;
2112 int indirectContextIndex = m_IndirectStorage.TryGetContextIndex(settings.viewInstanceID);
2113 if (indirectContextIndex >= 0)
2114 {
2115 IndirectBufferContext bufferCtx = m_IndirectStorage.GetBufferContext(indirectContextIndex);
2116
2117 // check what compute we need to do (if any)
2118 bool hasOccluders = occlusionCullingCommon.GetOccluderContext(settings.viewInstanceID, out OccluderContext occluderCtx);
2119
2120 // check we have occluders for all the required subviews, disable the occlusion test if not
2121 hasOccluders = hasOccluders && ((subviewSettings.occluderSubviewMask & occluderCtx.subviewValidMask) == subviewSettings.occluderSubviewMask);
2122
2123 IndirectBufferContext.BufferState newBufferState = IndirectBufferContext.BufferState.Zeroed;
2124 int newOccluderVersion = 0;
2125 int newSubviewMask = 0;
2126 switch (settings.occlusionTest)
2127 {
2128 case OcclusionTest.None:
2129 newBufferState = IndirectBufferContext.BufferState.NoOcclusionTest;
2130 break;
2131 case OcclusionTest.TestAll:
2132 if (hasOccluders)
2133 {
2134 newBufferState = IndirectBufferContext.BufferState.AllInstancesOcclusionTested;
2135 newOccluderVersion = occluderCtx.version;
2136 newSubviewMask = subviewSettings.occluderSubviewMask;
2137 }
2138 else
2139 {
2140 newBufferState = IndirectBufferContext.BufferState.NoOcclusionTest;
2141 }
2142 break;
2143 case OcclusionTest.TestCulled:
2144 if (hasOccluders)
2145 {
2146 bool hasMatchingCullingOutput = true;
2147 switch (bufferCtx.bufferState)
2148 {
2149 case IndirectBufferContext.BufferState.AllInstancesOcclusionTested:
2150 case IndirectBufferContext.BufferState.OccludedInstancesReTested:
2151 // valid or already done
2152 if (bufferCtx.subviewMask != subviewSettings.occluderSubviewMask)
2153 {
2154 Debug.Log("Expected an occlusion test of TestCulled to use the same subview mask as the previous occlusion test");
2155 hasMatchingCullingOutput = false;
2156 }
2157 break;
2158
2159 case IndirectBufferContext.BufferState.NoOcclusionTest:
2160 case IndirectBufferContext.BufferState.Zeroed:
2161 // no instances, keep the new buffer state zeroed
2162 hasMatchingCullingOutput = false;
2163 break;
2164
2165 default:
2166 // unexpected, keep the new buffer state zeroed
2167 hasMatchingCullingOutput = false;
2168 Debug.Log("Expected the previous occlusion test to be TestAll before using TestCulled");
2169 break;
2170 }
2171 if (hasMatchingCullingOutput)
2172 {
2173 newBufferState = IndirectBufferContext.BufferState.OccludedInstancesReTested;
2174 newOccluderVersion = occluderCtx.version;
2175 newSubviewMask = subviewSettings.occluderSubviewMask;
2176 }
2177 }
2178 break;
2179 }
2180
2181 // issue the work (if any)
2182 if (!bufferCtx.Matches(newBufferState, newOccluderVersion, newSubviewMask))
2183 {
2184 bool isFirstPass = (newBufferState == IndirectBufferContext.BufferState.AllInstancesOcclusionTested);
2185 bool isSecondPass = (newBufferState == IndirectBufferContext.BufferState.OccludedInstancesReTested);
2186
2187 bool doWait = (bufferCtx.bufferState == IndirectBufferContext.BufferState.Pending);
2188 bool doCopyInstances = (newBufferState == IndirectBufferContext.BufferState.NoOcclusionTest);
2189 bool doResetDraws = (bufferCtx.bufferState != IndirectBufferContext.BufferState.Zeroed) && !doCopyInstances;
2190 bool doCullInstances = (newBufferState != IndirectBufferContext.BufferState.Zeroed) && !doCopyInstances;
2191
2192 // sync before checking the allocation results
2193 if (doWait)
2194 bufferCtx.cullingJobHandle.Complete();
2195
2196 IndirectBufferAllocInfo allocInfo = m_IndirectStorage.GetAllocInfo(indirectContextIndex);
2197
2198 bufferCtx.bufferState = newBufferState;
2199 bufferCtx.occluderVersion = newOccluderVersion;
2200 bufferCtx.subviewMask = newSubviewMask;
2201
2202 if (!allocInfo.IsEmpty())
2203 {
2204 int debugCounterIndex = -1;
2205 if (m_DebugStats?.enabled ?? false)
2206 {
2207 debugCounterIndex = m_OcclusionEventDebugArray.TryAdd(
2208 settings.viewInstanceID,
2209 InstanceOcclusionEventType.OcclusionTest,
2210 newOccluderVersion,
2211 newSubviewMask,
2212 isFirstPass ? OcclusionTest.TestAll : isSecondPass ? OcclusionTest.TestCulled : OcclusionTest.None);
2213 }
2214
2215 // set up keywords
2216 bool occlusionDebug = false;
2217 if (isFirstPass || isSecondPass)
2218 {
2219 occlusionDebug = OcclusionCullingCommon.UseOcclusionDebug(in occluderCtx) && occluderHandles.occlusionDebugOverlay.IsValid();
2220 }
2221 var cs = m_OcclusionTestShader.cs;
2222 var firstPassKeyword = new LocalKeyword(cs, "OCCLUSION_FIRST_PASS");
2223 var secondPassKeyword = new LocalKeyword(cs, "OCCLUSION_SECOND_PASS");
2224 OccluderContext.SetKeyword(cmd, cs, firstPassKeyword, isFirstPass);
2225 OccluderContext.SetKeyword(cmd, cs, secondPassKeyword, isSecondPass);
2226
2227 m_ShaderVariables[0] = new InstanceOcclusionCullerShaderVariables
2228 {
2229 _DrawInfoAllocIndex = (uint)allocInfo.drawAllocIndex,
2230 _DrawInfoCount = (uint)allocInfo.drawCount,
2231 _InstanceInfoAllocIndex = (uint)(IndirectBufferContextStorage.kInstanceInfoGpuOffsetMultiplier * allocInfo.instanceAllocIndex),
2232 _InstanceInfoCount = (uint)allocInfo.instanceCount,
2233 _BoundingSphereInstanceDataAddress = batchersContext.renderersParameters.boundingSphere.gpuAddress,
2234 _DebugCounterIndex = debugCounterIndex,
2235 _InstanceMultiplierShift = (settings.instanceMultiplier == 2) ? 1 : 0,
2236 };
2237 cmd.SetBufferData(m_ConstantBuffer, m_ShaderVariables);
2238 cmd.SetComputeConstantBufferParam(cs, ShaderIDs.InstanceOcclusionCullerShaderVariables, m_ConstantBuffer, 0, m_ConstantBuffer.stride);
2239
2240 occlusionCullingCommon.PrepareCulling(cmd, in occluderCtx, settings, subviewSettings, m_OcclusionTestShader, occlusionDebug);
2241
2242 if (doCopyInstances)
2243 {
2244 int kernel = m_CopyInstancesKernel;
2245 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawInfo, m_IndirectStorage.drawInfoBuffer);
2246 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceInfo, m_IndirectStorage.instanceInfoBuffer);
2247 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawArgs, m_IndirectStorage.argsBuffer);
2248 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceIndices, m_IndirectStorage.instanceBuffer);
2249
2250 cmd.DispatchCompute(cs, kernel, (allocInfo.instanceCount + 63) / 64, 1, 1);
2251 }
2252
2253 if (doResetDraws)
2254 {
2255 int kernel = m_ResetDrawArgsKernel;
2256 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawInfo, bufferHandles.drawInfoBuffer);
2257 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawArgs, bufferHandles.argsBuffer);
2258 cmd.DispatchCompute(cs, kernel, (allocInfo.drawCount + 63) / 64, 1, 1);
2259 }
2260
2261 if (doCullInstances)
2262 {
2263 int kernel = m_CullInstancesKernel;
2264 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawInfo, bufferHandles.drawInfoBuffer);
2265 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceInfo, bufferHandles.instanceInfoBuffer);
2266 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._DrawArgs, bufferHandles.argsBuffer);
2267 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceIndices, bufferHandles.instanceBuffer);
2268 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._InstanceDataBuffer, batchersContext.gpuInstanceDataBuffer);
2269 cmd.SetComputeBufferParam(cs, kernel, ShaderIDs._OcclusionDebugCounters, m_OcclusionEventDebugArray.CounterBuffer);
2270
2271 if (isFirstPass || isSecondPass)
2272 OcclusionCullingCommon.SetDepthPyramid(cmd, m_OcclusionTestShader, kernel, occluderHandles);
2273
2274 if (occlusionDebug)
2275 OcclusionCullingCommon.SetDebugPyramid(cmd, m_OcclusionTestShader, kernel, occluderHandles);
2276
2277 if (isSecondPass)
2278 cmd.DispatchCompute(cs, kernel, bufferHandles.argsBuffer, (uint)(GraphicsBuffer.IndirectDrawIndexedArgs.size * allocInfo.GetExtraDrawInfoSlotIndex()));
2279 else
2280 cmd.DispatchCompute(cs, kernel, (allocInfo.instanceCount + 63) / 64, 1, 1);
2281 }
2282 }
2283 }
2284
2285 // update to the new buffer state
2286 m_IndirectStorage.SetBufferContext(indirectContextIndex, bufferCtx);
2287 }
2288 }
2289
2290 private void FlushDebugCounters()
2291 {
2292 if (m_DebugStats?.enabled ?? false)
2293 {
2294 m_SplitDebugArray.MoveToDebugStatsAndClear(m_DebugStats);
2295 m_OcclusionEventDebugArray.MoveToDebugStatsAndClear(m_DebugStats);
2296 }
2297 }
2298
2299 private void OnBeginSceneViewCameraRendering()
2300 {
2301#if UNITY_EDITOR
2302 m_IsSceneViewCamera = true;
2303#endif
2304 }
2305
2306 private void OnEndSceneViewCameraRendering()
2307 {
2308#if UNITY_EDITOR
2309 m_IsSceneViewCamera = false;
2310#endif
2311 }
2312
2313 public void UpdateFrame()
2314 {
2315 DisposeSceneViewHiddenBits();
2316 DisposeCompactVisibilityMasks();
2317 FlushDebugCounters();
2318 m_IndirectStorage.ClearContextsAndGrowBuffers();
2319 }
2320
2321 public void OnBeginCameraRendering(Camera camera)
2322 {
2323 if (camera.cameraType == CameraType.SceneView)
2324 OnBeginSceneViewCameraRendering();
2325 }
2326
2327 public void OnEndCameraRendering(Camera camera)
2328 {
2329 if (camera.cameraType == CameraType.SceneView)
2330 OnEndSceneViewCameraRendering();
2331 }
2332
2333 public void Dispose()
2334 {
2335 DisposeSceneViewHiddenBits();
2336 DisposeCompactVisibilityMasks();
2337 m_IndirectStorage.Dispose();
2338 m_DebugStats = null;
2339 m_OcclusionEventDebugArray.Dispose();
2340 m_SplitDebugArray.Dispose();
2341 m_ShaderVariables.Dispose();
2342 m_ConstantBuffer.Release();
2343 m_CommandBuffer.Dispose();
2344 }
2345 }
2346}