A game about forced loneliness, made by TACStudios
at master 2346 lines 114 kB view raw
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}