Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,221 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using UnityEngine.Assertions;
using Unity.Collections;
using Unity.Jobs;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Profiling;
using Unity.Profiling.LowLevel;
namespace UnityEngine.Rendering
{
internal class CPUDrawInstanceData
{
/// <summary>
/// Sorts and removes the destroyed draw instance indices from the CPU draw instance, batch, and
/// range data structures using binary search. Cost scales with total live draw instance count, not just the number removed.
/// </summary>
static readonly ProfilerMarker k_DestroyDrawInstanceIndices =
new ProfilerMarker(ProfilerCategory.Render, "DestroyDrawInstanceIndices.RemoveDrawInstanceIndices", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Removes a batch of GPU instances from all CPU draw data structures. Sorts instance
/// handles, maps them to draw instance indices via a parallel job, then removes those
/// indices. Cost scales with total live draw instance count, not just the number removed.
/// </summary>
static readonly ProfilerMarker k_DestroyDrawInstances =
new ProfilerMarker(ProfilerCategory.Render, "DestroyDrawInstances", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Sorts the destroyed instance handle array in parallel. A prerequisite for the
/// binary-search index-lookup job in DestroyDrawInstances.
/// </summary>
static readonly ProfilerMarker k_DestroyDrawInstancesParallelSort =
new ProfilerMarker(ProfilerCategory.Render, "DestroyDrawInstances.ParallelSort", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Sorts the destroyed batch material ID array in parallel. A prerequisite for the
/// binary-search index-lookup job in DestroyMaterialDrawInstances.
/// </summary>
static readonly ProfilerMarker k_DestroyedBatchMaterialsParallelSort =
new ProfilerMarker(ProfilerCategory.Render, "DestroyedBatchMaterials.ParallelSort", MarkerFlags.VerbosityAdvanced);
public NativeList<DrawInstance> drawInstances => m_DrawInstances;
public NativeParallelHashMap<DrawKey, int> batchHash => m_BatchHash;
public NativeList<DrawBatch> drawBatches => m_DrawBatches;
public NativeParallelHashMap<RangeKey, int> rangeHash => m_RangeHash;
public NativeList<DrawRange> drawRanges => m_DrawRanges;
public NativeArray<int> drawBatchIndices => m_DrawBatchIndices.AsArray();
public NativeArray<int> drawInstanceIndices => m_DrawInstanceIndices.AsArray();
private NativeParallelHashMap<RangeKey, int> m_RangeHash; // index in m_DrawRanges, hashes by range state
private NativeList<DrawRange> m_DrawRanges;
private NativeParallelHashMap<DrawKey, int> m_BatchHash; // index in m_DrawBatches, hashed by draw state
private NativeList<DrawBatch> m_DrawBatches;
private NativeList<DrawInstance> m_DrawInstances;
private NativeList<int> m_DrawInstanceIndices; // DOTS instance index, arranged in contiguous blocks in m_DrawBatches order (see DrawBatch.instanceOffset, DrawBatch.instanceCount)
private NativeList<int> m_DrawBatchIndices; // index in m_DrawBatches, arranged in contiguous blocks in m_DrawRanges order (see DrawRange.drawOffset, DrawRange.drawCount)
private bool m_NeedsRebuild;
public bool valid => m_DrawInstances.IsCreated;
public void Initialize()
{
Assert.IsTrue(!valid);
m_RangeHash = new NativeParallelHashMap<RangeKey, int>(1024, Allocator.Persistent);
m_DrawRanges = new NativeList<DrawRange>(Allocator.Persistent);
m_BatchHash = new NativeParallelHashMap<DrawKey, int>(1024, Allocator.Persistent);
m_DrawBatches = new NativeList<DrawBatch>(Allocator.Persistent);
m_DrawInstances = new NativeList<DrawInstance>(1024, Allocator.Persistent);
m_DrawInstanceIndices = new NativeList<int>(1024, Allocator.Persistent);
m_DrawBatchIndices = new NativeList<int>(1024, Allocator.Persistent);
}
public void Dispose()
{
if (m_DrawBatchIndices.IsCreated)
m_DrawBatchIndices.Dispose();
if (m_DrawInstanceIndices.IsCreated)
m_DrawInstanceIndices.Dispose();
if (m_DrawInstances.IsCreated)
m_DrawInstances.Dispose();
if (m_DrawBatches.IsCreated)
m_DrawBatches.Dispose();
if (m_BatchHash.IsCreated)
m_BatchHash.Dispose();
if (m_DrawRanges.IsCreated)
m_DrawRanges.Dispose();
if (m_RangeHash.IsCreated)
m_RangeHash.Dispose();
}
public void RebuildDrawListsIfNeeded()
{
if (!m_NeedsRebuild)
return;
m_NeedsRebuild = false;
Assert.IsTrue(m_RangeHash.Count() == m_DrawRanges.Length);
Assert.IsTrue(m_BatchHash.Count() == m_DrawBatches.Length);
m_DrawInstanceIndices.ResizeUninitialized(m_DrawInstances.Length);
m_DrawBatchIndices.ResizeUninitialized(m_DrawBatches.Length);
var internalDrawIndex = new NativeArray<int>(drawBatches.Length * BuildDrawListsJob.k_IntsPerCacheLine, Allocator.TempJob, NativeArrayOptions.ClearMemory);
var prefixSumDrawInstancesJob = new PrefixSumDrawInstancesJob()
{
rangeHash = m_RangeHash,
drawRanges = m_DrawRanges,
drawBatches = m_DrawBatches,
drawBatchIndices = m_DrawBatchIndices.AsArray()
};
var prefixSumJobHandle = prefixSumDrawInstancesJob.Schedule();
new BuildDrawListsJob()
{
drawInstances = m_DrawInstances,
batchHash = m_BatchHash,
drawBatches = m_DrawBatches,
internalDrawIndex = internalDrawIndex,
drawInstanceIndices = m_DrawInstanceIndices.AsArray(),
}
.RunParallel(m_DrawInstances.Length, 128, prefixSumJobHandle);
internalDrawIndex.Dispose();
}
public unsafe void DestroyDrawInstanceIndices(NativeArray<int> drawInstanceIndicesToDestroy)
{
using var _ = k_DestroyDrawInstanceIndices.Auto();
drawInstanceIndicesToDestroy.ParallelSort().Complete();
CPUDrawInstanceDataBurst.RemoveDrawInstanceIndices(drawInstanceIndicesToDestroy,
ref m_DrawInstances,
ref m_RangeHash,
ref m_BatchHash,
ref m_DrawRanges,
ref m_DrawBatches);
}
public unsafe void DestroyDrawInstances(NativeArray<InstanceHandle> destroyedInstances)
{
if (m_DrawInstances.IsEmpty || destroyedInstances.Length == 0)
return;
using var _ = k_DestroyDrawInstances.Auto();
NeedsRebuild();
var destroyedInstancesSorted = new NativeArray<InstanceHandle>(destroyedInstances, Allocator.TempJob);
Assert.AreEqual(UnsafeUtility.SizeOf<InstanceHandle>(), UnsafeUtility.SizeOf<int>());
using (k_DestroyDrawInstancesParallelSort.Auto())
{
destroyedInstancesSorted.Reinterpret<int>().ParallelSort().Complete();
}
var drawInstanceIndicesToDestroy = new NativeList<int>(m_DrawInstances.Length, Allocator.TempJob);
new FindDrawInstancesJob()
{
instancesSorted = destroyedInstancesSorted,
drawInstances = m_DrawInstances,
outDrawInstanceIndicesWriter = drawInstanceIndicesToDestroy.AsParallelWriter()
}
.RunBatchParallel(m_DrawInstances.Length, FindDrawInstancesJob.k_MaxBatchSize);
DestroyDrawInstanceIndices(drawInstanceIndicesToDestroy.AsArray());
destroyedInstancesSorted.Dispose();
drawInstanceIndicesToDestroy.Dispose();
}
public unsafe void DestroyMaterialDrawInstances(NativeArray<uint> destroyedBatchMaterials)
{
if (m_DrawInstances.IsEmpty || destroyedBatchMaterials.Length == 0)
return;
NeedsRebuild();
var destroyedBatchMaterialsSorted = new NativeArray<uint>(destroyedBatchMaterials, Allocator.TempJob);
using (k_DestroyedBatchMaterialsParallelSort.Auto())
{
destroyedBatchMaterialsSorted.Reinterpret<int>().ParallelSort().Complete();
}
var drawInstanceIndicesToDestroy = new NativeList<int>(m_DrawInstances.Length, Allocator.TempJob);
new FindMaterialDrawInstancesJob()
{
materialsSorted = destroyedBatchMaterialsSorted,
drawInstances = m_DrawInstances,
outDrawInstanceIndicesWriter = drawInstanceIndicesToDestroy.AsParallelWriter()
}
.RunBatchParallel(m_DrawInstances.Length, FindMaterialDrawInstancesJob.k_MaxBatchSize);
DestroyDrawInstanceIndices(drawInstanceIndicesToDestroy.AsArray());
destroyedBatchMaterialsSorted.Dispose();
drawInstanceIndicesToDestroy.Dispose();
}
public void NeedsRebuild()
{
m_NeedsRebuild = true;
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8967952de1d564742ae0197935b63bd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,74 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using UnityEngine.Rendering;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
[BurstCompile]
internal static class CPUDrawInstanceDataBurst
{
private static void RemoveDrawRange(in RangeKey key, ref NativeParallelHashMap<RangeKey, int> rangeHash, ref NativeList<DrawRange> drawRanges)
{
int drawRangeIndex = rangeHash[key];
ref DrawRange lastDrawRange = ref drawRanges.ElementAt(drawRanges.Length - 1);
rangeHash[lastDrawRange.key] = drawRangeIndex;
rangeHash.Remove(key);
drawRanges.RemoveAtSwapBack(drawRangeIndex);
}
private static void RemoveDrawBatch(in DrawKey key, ref NativeParallelHashMap<RangeKey, int> rangeHash, ref NativeParallelHashMap<DrawKey, int> batchHash,
ref NativeList<DrawRange> drawRanges, ref NativeList<DrawBatch> drawBatches)
{
int drawBatchIndex = batchHash[key];
ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
int drawRangeIndex = rangeHash[key.range];
ref DrawRange drawRange = ref drawRanges.ElementAt(drawRangeIndex);
Assert.IsTrue(drawRange.drawCount > 0);
if (--drawRange.drawCount == 0)
RemoveDrawRange(drawRange.key, ref rangeHash, ref drawRanges);
ref DrawBatch lastDrawBatch = ref drawBatches.ElementAt(drawBatches.Length - 1);
batchHash[lastDrawBatch.key] = drawBatchIndex;
batchHash.Remove(key);
drawBatches.RemoveAtSwapBack(drawBatchIndex);
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
public static unsafe void RemoveDrawInstanceIndices(in NativeArray<int> drawInstanceIndicesSorted, ref NativeList<DrawInstance> drawInstances, ref NativeParallelHashMap<RangeKey, int> rangeHash,
ref NativeParallelHashMap<DrawKey, int> batchHash, ref NativeList<DrawRange> drawRanges, ref NativeList<DrawBatch> drawBatches)
{
var drawInstancesPtr = (DrawInstance*)drawInstances.GetUnsafePtr();
var drawInstancesNewBack = drawInstances.Length - 1;
for (int indexRev = drawInstanceIndicesSorted.Length - 1; indexRev >= 0; --indexRev)
{
int indexToRemove = drawInstanceIndicesSorted[indexRev];
DrawInstance* drawInstance = drawInstancesPtr + indexToRemove;
int drawBatchIndex = batchHash[drawInstance->key];
ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
Assert.IsTrue(drawBatch.instanceCount > 0);
if (--drawBatch.instanceCount == 0)
RemoveDrawBatch(drawBatch.key, ref rangeHash, ref batchHash, ref drawRanges, ref drawBatches);
UnsafeUtility.MemCpy(drawInstance, drawInstancesPtr + drawInstancesNewBack--, sizeof(DrawInstance));
}
drawInstances.ResizeUninitialized(drawInstancesNewBack + 1);
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f89188da7ac3dd74e920a8a99c1c0de1
@@ -0,0 +1,237 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using System.Threading;
using UnityEngine.Assertions;
using Unity.Collections;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using Unity.Mathematics;
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.FindNonRegisteredInstanceIDsJob<UnityEngine.Rendering.MeshInfo>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.FindNonRegisteredInstanceIDsJob<UnityEngine.Rendering.GPUDrivenMaterial>))]
namespace UnityEngine.Rendering
{
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct PrefixSumDrawInstancesJob : IJob
{
[ReadOnly] public NativeParallelHashMap<RangeKey, int> rangeHash;
public NativeList<DrawRange> drawRanges;
public NativeList<DrawBatch> drawBatches;
public NativeArray<int> drawBatchIndices;
public void Execute()
{
Assert.AreEqual(rangeHash.Count(), drawRanges.Length);
Assert.AreEqual(drawBatchIndices.Length, drawBatches.Length);
// Prefix sum to calculate draw offsets for each DrawRange
int drawPrefixSum = 0;
for (int i = 0; i < drawRanges.Length; ++i)
{
ref DrawRange drawRange = ref drawRanges.ElementAt(i);
drawRange.drawOffset = drawPrefixSum;
drawPrefixSum += drawRange.drawCount;
}
// Generate DrawBatch index ranges for each DrawRange
var internalRangeIndex = new NativeArray<int>(drawRanges.Length, Allocator.Temp);
for (int i = 0; i < drawBatches.Length; ++i)
{
ref DrawBatch drawBatch = ref drawBatches.ElementAt(i);
Assert.IsTrue(drawBatch.instanceCount > 0);
if (rangeHash.TryGetValue(drawBatch.key.range, out int drawRangeIndex))
{
ref DrawRange drawRange = ref drawRanges.ElementAt(drawRangeIndex);
drawBatchIndices[drawRange.drawOffset + internalRangeIndex[drawRangeIndex]] = i;
internalRangeIndex[drawRangeIndex]++;
}
}
// Prefix sum to calculate instance offsets for each DrawCommand
int drawInstancesPrefixSum = 0;
for (int i = 0; i < drawBatchIndices.Length; ++i)
{
// DrawIndices remap to get DrawCommands ordered by DrawRange
var drawBatchIndex = drawBatchIndices[i];
ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
drawBatch.instanceOffset = drawInstancesPrefixSum;
drawInstancesPrefixSum += drawBatch.instanceCount;
}
internalRangeIndex.Dispose();
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal unsafe struct BuildDrawListsJob : IJobParallelFor
{
public const int k_IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int);
[ReadOnly] public NativeParallelHashMap<DrawKey, int> batchHash;
[NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
[NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawBatch> drawBatches;
[NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> internalDrawIndex;
[NativeDisableContainerSafetyRestriction, NoAlias] [WriteOnly] public NativeArray<int> drawInstanceIndices;
private unsafe static int IncrementCounter(int* counter)
{
return Interlocked.Increment(ref UnsafeUtility.AsRef<int>(counter)) - 1;
}
public void Execute(int index)
{
// Generate instance index ranges for each DrawCommand
ref DrawInstance drawInstance = ref drawInstances.ElementAt(index);
int drawBatchIndex = batchHash[drawInstance.key];
ref DrawBatch drawBatch = ref drawBatches.ElementAt(drawBatchIndex);
var offset = IncrementCounter((int*)internalDrawIndex.GetUnsafePtr() + drawBatchIndex * k_IntsPerCacheLine);
var writeIndex = drawBatch.instanceOffset + offset;
drawInstanceIndices[writeIndex] = drawInstance.instanceIndex;
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal unsafe struct FindDrawInstancesJob : IJobParallelForBatch
{
public const int k_MaxBatchSize = 128;
[ReadOnly] public NativeArray<InstanceHandle> instancesSorted;
[NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
[WriteOnly] public NativeList<int>.ParallelWriter outDrawInstanceIndicesWriter;
public void Execute(int startIndex, int count)
{
int* instancesToRemovePtr = stackalloc int[k_MaxBatchSize];
var instancesToRemove = new UnsafeList<int>(instancesToRemovePtr, k_MaxBatchSize);
instancesToRemove.Length = 0;
for (int i = startIndex; i < startIndex + count; ++i)
{
ref DrawInstance drawInstance = ref drawInstances.ElementAt(i);
if (instancesSorted.BinarySearch(InstanceHandle.Create(drawInstance.instanceIndex)) >= 0)
instancesToRemove.AddNoResize(i);
}
outDrawInstanceIndicesWriter.AddRangeNoResize(instancesToRemove);
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal unsafe struct FindMaterialDrawInstancesJob : IJobParallelForBatch
{
public const int k_MaxBatchSize = 128;
[ReadOnly] public NativeArray<uint> materialsSorted;
[NativeDisableContainerSafetyRestriction, NoAlias] [ReadOnly] public NativeList<DrawInstance> drawInstances;
[WriteOnly] public NativeList<int>.ParallelWriter outDrawInstanceIndicesWriter;
public void Execute(int startIndex, int count)
{
int* instancesToRemovePtr = stackalloc int[k_MaxBatchSize];
var instancesToRemove = new UnsafeList<int>(instancesToRemovePtr, k_MaxBatchSize);
instancesToRemove.Length = 0;
for (int i = startIndex; i < startIndex + count; ++i)
{
ref DrawInstance drawInstance = ref drawInstances.ElementAt(i);
if (materialsSorted.BinarySearch(drawInstance.key.materialID.value) >= 0)
instancesToRemove.AddNoResize(i);
}
outDrawInstanceIndicesWriter.AddRangeNoResize(instancesToRemove);
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct FindNonRegisteredInstanceIDsJob<T> : IJobParallelFor where T : unmanaged
{
public const int MaxBatchSize = 128;
[ReadOnly] public NativeArray<JaggedJobRange> jobRanges;
[ReadOnly] public JaggedSpan<EntityId> jaggedInstanceIDs;
[ReadOnly] public NativeParallelHashMap<EntityId, T> hashMap;
[WriteOnly] public NativeParallelHashSet<EntityId>.ParallelWriter outInstanceIDWriter;
public unsafe void Execute(int jobIndex)
{
JaggedJobRange jobRange = jobRanges[jobIndex];
NativeArray<EntityId> instanceIDs = jaggedInstanceIDs[jobRange.sectionIndex];
for (int i = jobRange.localStart; i < jobRange.localEnd; ++i)
{
EntityId instanceID = instanceIDs[i];
if (!hashMap.ContainsKey(instanceID))
outInstanceIDWriter.Add(instanceID);
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RegisterNewMaterialsJob : IJobParallelFor
{
[ReadOnly] public NativeArray<EntityId> instanceIDs;
[ReadOnly] public NativeArray<GPUDrivenMaterial> materials;
[WriteOnly] public NativeParallelHashMap<EntityId, GPUDrivenMaterial>.ParallelWriter materialMap;
public unsafe void Execute(int index)
{
bool success = materialMap.TryAdd(instanceIDs[index], materials.ElementAt(index));
Assert.IsTrue(success);
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RegisterNewMeshesJob : IJobParallelFor
{
[ReadOnly] public NativeArray<EntityId> instanceIDs;
[ReadOnly] public NativeArray<BatchMeshID> batchMeshIDs;
[ReadOnly] public NativeArray<GPUDrivenMeshData> meshDatas;
[ReadOnly] public NativeArray<int> subMeshOffsets;
[ReadOnly] public NativeArray<GPUDrivenSubMesh> subMeshBuffer;
[WriteOnly] public NativeParallelHashMap<EntityId, MeshInfo>.ParallelWriter meshMap;
public unsafe void Execute(int index)
{
Assert.IsTrue(instanceIDs.Length == meshDatas.Length);
Assert.IsTrue(instanceIDs.Length == subMeshOffsets.Length);
EntityId instanceID = instanceIDs[index];
int subMeshOffset = subMeshOffsets[index];
GPUDrivenMeshData meshData = meshDatas[index];
BatchMeshID batchMeshID = batchMeshIDs[index];
int totalSubMeshCount = meshData.subMeshCount * math.max(meshData.meshLodCount, 1);
NativeArray<GPUDrivenSubMesh> subMeshes = subMeshBuffer.GetSubArray(subMeshOffset, totalSubMeshCount);
var embeddedSubMeshes = new EmbeddedArray64<GPUDrivenSubMesh>(subMeshes, Allocator.Persistent);
MeshInfo meshInfo = default;
meshInfo.meshID = batchMeshID;
meshInfo.meshLodCount = meshData.meshLodCount;
meshInfo.meshLodSelectionCurve = meshData.meshLodSelectionCurve;
meshInfo.subMeshes = embeddedSubMeshes;
bool success = meshMap.TryAdd(instanceID, meshInfo);
Assert.IsTrue(success);
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84f2d5310507cd645a110b652fbfd1d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,540 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using Unity.Collections;
using Unity.Jobs;
using Unity.Profiling;
using Unity.Profiling.LowLevel;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
internal delegate void OnCullingCompleteCallback(JobHandle jobHandle, in BatchCullingContext cullingContext, in BatchCullingOutput cullingOutput);
internal struct MeshInfo
{
public BatchMeshID meshID;
public int meshLodCount;
public Mesh.LodSelectionCurve meshLodSelectionCurve;
public EmbeddedArray64<GPUDrivenSubMesh> subMeshes;
public bool isLodSelectionActive => meshLodCount > 1;
public bool EqualsSourceData(in GPUDrivenMeshData otherData, NativeArray<GPUDrivenSubMesh> otherSubMeshes)
{
if (meshLodCount != otherData.meshLodCount)
return false;
if (meshLodSelectionCurve.lodBias != otherData.meshLodSelectionCurve.lodBias)
return false;
if (meshLodSelectionCurve.lodSlope != otherData.meshLodSelectionCurve.lodSlope)
return false;
if (subMeshes.Length != otherSubMeshes.Length)
return false;
for (int i = 0; i < subMeshes.Length; i++)
{
GPUDrivenSubMesh oldSubMesh = subMeshes[i];
GPUDrivenSubMesh newSubMesh = otherSubMeshes[i];
if (!oldSubMesh.Equals(newSubMesh))
return false;
}
return true;
}
}
internal class InstanceCullingBatcher : IDisposable
{
/// <summary>
/// Filters the incoming mesh IDs to find previously unseen ones, registers them with the
/// BatchRendererGroup, then fetches and stores their sub-mesh data. Cost scales with the
/// number of unique new meshes and their sub-mesh counts.
/// </summary>
static readonly ProfilerMarker k_RegisterMeshes =
new ProfilerMarker(ProfilerCategory.Render, "RegisterMeshes", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Filters the incoming material IDs to find previously unseen ones, then registers them
/// with the BatchRendererGroup. Cost scales with the number of unique new materials.
/// </summary>
static readonly ProfilerMarker k_RegisterMaterials =
new ProfilerMarker(ProfilerCategory.Render, "RegisterMaterials", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Unregisters each destroyed material from the BatchRendererGroup, removes it from the
/// material map, and removes all associated draw instances from CPU draw data structures.
/// </summary>
static readonly ProfilerMarker k_DestroyMaterials =
new ProfilerMarker(ProfilerCategory.Render, "DestroyMaterials", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Unregisters each destroyed mesh from the BatchRendererGroup, disposes its sub-mesh
/// data, and removes it from the mesh map.
/// </summary>
static readonly ProfilerMarker k_DestroyMeshes =
new ProfilerMarker(ProfilerCategory.Render, "DestroyMeshes", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Removes stale draw instances for the given instances, then creates new draw batches
/// by grouping instances by mesh, material, and render state. Marks the draw lists dirty
/// for rebuild on next cull.
/// </summary>
internal static readonly ProfilerMarker k_BuildBatches =
new ProfilerMarker(ProfilerCategory.Render, "BuildBatches", MarkerFlags.VerbosityAdvanced);
/// <summary>
/// Registers any new meshes and materials in the update batch, then rebuilds draw batches
/// for the affected instances. Combines RegisterMeshes, RegisterMaterials,
/// and BuildBatches in a single scope.
/// </summary>
static readonly ProfilerMarker k_RegisterAndBuildBatches =
new ProfilerMarker(ProfilerCategory.Render, "RegisterAndBuildBatches");
/// <summary>
/// Registers any newly-encountered materials and meshes with the BatchRendererGroup
/// for an update batch, before draw batches that reference them are built. The
/// surrounding BuildBatches step is intentionally excluded so the recorder doesn't
/// double-count with <c>k_BuildBatches</c>.
/// </summary>
internal static readonly ProfilerMarker k_RegisterMaterialsAndMeshes =
new ProfilerMarker(ProfilerCategory.Render, "GRD.RegisterMaterialsAndMeshes");
/// <summary>
/// Schedules the per-frame culling job tree (frustum, LOD, occlusion). Covers the
/// CPU-side dispatch and scheduling cost only; the actual worker-thread job execution
/// is not included.
/// </summary>
internal static readonly ProfilerMarker k_CreateCullJobTree =
new ProfilerMarker(ProfilerCategory.Render, "GRD.CreateCullJobTree");
private GPUResidentContext m_GRDContext;
private InstanceDataSystem m_InstanceDataSystem;
private LODGroupDataSystem m_LODGroupDataSystem;
private CPUDrawInstanceData m_DrawInstanceData;
private BatchRendererGroup m_BRG;
private OnCullingCompleteCallback m_OnCompleteCallback;
private NativeParallelHashMap<GPUArchetypeHandle, BatchID> m_BatchIDs;
private NativeParallelHashMap<EntityId, GPUDrivenMaterial> m_MaterialMap;
private NativeParallelHashMap<EntityId, MeshInfo> m_MeshMap;
private int m_CachedInstanceDataBufferLayoutVersion;
private NativeArray<BatchMeshID> m_TempBatchMeshIDs;
private NativeHashSet<EntityId> m_TempChangedMeshIDs;
public NativeParallelHashMap<EntityId, GPUDrivenMaterial> materialMap => m_MaterialMap;
public NativeParallelHashMap<EntityId, MeshInfo> meshMap => m_MeshMap;
public void Initialize(GPUResidentContext grdContext,
in GPUResidentDrawerSettings settings,
BatchRendererGroup.OnFinishedCulling onFinishedCulling,
OnCullingCompleteCallback onCompleteCallback = null)
{
m_GRDContext = grdContext;
m_InstanceDataSystem = grdContext.instanceDataSystem;
m_LODGroupDataSystem = grdContext.lodGroupDataSystem;
m_DrawInstanceData = new CPUDrawInstanceData();
m_DrawInstanceData.Initialize();
m_BRG = new BatchRendererGroup(new BatchRendererGroupCreateInfo
{
cullingCallback = OnPerformCulling,
finishedCullingCallback = onFinishedCulling,
userContext = IntPtr.Zero
});
#if UNITY_EDITOR
if (settings.pickingShader != null)
{
var mat = new Material(settings.pickingShader);
mat.hideFlags = HideFlags.HideAndDontSave;
m_BRG.SetPickingMaterial(mat);
}
if (settings.loadingShader != null)
{
var mat = new Material(settings.loadingShader);
mat.hideFlags = HideFlags.HideAndDontSave;
m_BRG.SetLoadingMaterial(mat);
}
if (settings.errorShader != null)
{
var mat = new Material(settings.errorShader);
mat.hideFlags = HideFlags.HideAndDontSave;
m_BRG.SetErrorMaterial(mat);
}
m_BRG.SetEnabledViewTypes(new BatchCullingViewType[]
{
BatchCullingViewType.Light,
BatchCullingViewType.Camera,
BatchCullingViewType.Picking,
BatchCullingViewType.SelectionOutline,
BatchCullingViewType.Filtering
});
#endif
m_CachedInstanceDataBufferLayoutVersion = -1;
m_OnCompleteCallback = onCompleteCallback;
m_MaterialMap = new NativeParallelHashMap<EntityId, GPUDrivenMaterial>(64, Allocator.Persistent);
m_MeshMap = new NativeParallelHashMap<EntityId, MeshInfo>(64, Allocator.Persistent);
m_BatchIDs = new NativeParallelHashMap<GPUArchetypeHandle, BatchID>(8, Allocator.Persistent);
m_InstanceDataSystem.onGPUBufferLayoutChanged += UpdateInstanceDataBufferLayoutVersion;
}
public void Dispose()
{
m_OnCompleteCallback = null;
m_InstanceDataSystem.onGPUBufferLayoutChanged -= UpdateInstanceDataBufferLayoutVersion;
foreach (var batchID in m_BatchIDs)
{
m_BRG.RemoveBatch(batchID.Value);
}
m_BatchIDs.Dispose();
if (m_BRG != null)
m_BRG.Dispose();
m_DrawInstanceData.Dispose();
m_DrawInstanceData = null;
m_MaterialMap.Dispose();
foreach (var kv in m_MeshMap)
kv.Value.subMeshes.Dispose();
m_MeshMap.Dispose();
}
private BatchID AddBatch(GPUArchetypeHandle archetype)
{
if (m_CachedInstanceDataBufferLayoutVersion != m_InstanceDataSystem.gpuBufferLayoutVersion)
return BatchID.Null;
var instanceDataBuffer = m_InstanceDataSystem.gpuBuffer;
var archetypeDesc = m_InstanceDataSystem.archetypeManager.GetRef().GetArchetypeDesc(archetype);
var components = archetypeDesc.components;
var metadatas = new NativeArray<MetadataValue>(components.Length, Allocator.Temp);
for (int i = 0; i < metadatas.Length; ++i)
{
var componentHandle = components[i];
var compIndex = instanceDataBuffer.GetComponentIndex(componentHandle).index;
var metadata = m_InstanceDataSystem.gpuBuffer.componentsMetadata[compIndex];
metadatas[i] = metadata;
}
return m_BRG.AddBatch(metadatas, m_InstanceDataSystem.gpuBufferHandle);
}
private void UpdateInstanceDataBufferLayoutVersion()
{
if (m_CachedInstanceDataBufferLayoutVersion != m_InstanceDataSystem.gpuBufferLayoutVersion)
{
m_CachedInstanceDataBufferLayoutVersion = m_InstanceDataSystem.gpuBufferLayoutVersion;
foreach (var kv in m_BatchIDs)
{
var batchID = kv.Value;
m_BRG.RemoveBatch(batchID);
}
m_BatchIDs.Clear();
var archetypeManager = m_InstanceDataSystem.archetypeManager;
var archetypeCount = archetypeManager.GetRef().GetArchetypesCount();
for (int i = 0; i < archetypeCount; i++)
{
var archetype = GPUArchetypeHandle.Create((short)i);
var batchID = AddBatch(archetype);
m_BatchIDs.Add(archetype, batchID);
}
}
}
private void OnFetchMeshesDataForRegistration(NativeArray<EntityId> meshIDs,
NativeArray<GPUDrivenMeshData> meshDatas,
NativeArray<int> subMeshOffsets,
NativeArray<GPUDrivenSubMesh> subMeshes)
{
Assert.IsTrue(meshIDs.Length == meshDatas.Length);
Assert.IsTrue(m_TempBatchMeshIDs.Length == meshIDs.Length);
new RegisterNewMeshesJob
{
meshMap = m_MeshMap.AsParallelWriter(),
instanceIDs = meshIDs,
batchMeshIDs = m_TempBatchMeshIDs,
meshDatas = meshDatas,
subMeshOffsets = subMeshOffsets,
subMeshBuffer = subMeshes,
}
.RunParallel(meshIDs.Length, 128);
}
private void RegisterMeshes(JaggedSpan<EntityId> meshIDs)
{
using var _ = k_RegisterMeshes.Auto();
var jobRanges = JaggedJobRange.FromSpanWithMaxBatchSize(meshIDs, FindNonRegisteredInstanceIDsJob<MeshInfo>.MaxBatchSize, Allocator.TempJob);
var newMeshSet = new NativeParallelHashSet<EntityId>(meshIDs.totalLength, Allocator.TempJob);
new FindNonRegisteredInstanceIDsJob<MeshInfo>
{
jobRanges = jobRanges.AsArray(),
jaggedInstanceIDs = meshIDs,
hashMap = m_MeshMap,
outInstanceIDWriter = newMeshSet.AsParallelWriter()
}
.RunParallel(jobRanges);
if (!newMeshSet.IsEmpty)
{
NativeArray<EntityId> newMeshIDs = newMeshSet.ToNativeArray(Allocator.TempJob);
var batchMeshIDs = new NativeArray<BatchMeshID>(newMeshIDs.Length, Allocator.TempJob);
GPUDrivenProcessor.RegisterMeshes(m_BRG, newMeshIDs, batchMeshIDs);
int totalMeshesNum = m_MeshMap.Count() + newMeshIDs.Length;
m_MeshMap.Capacity = Math.Max(m_MeshMap.Capacity, Mathf.CeilToInt(totalMeshesNum / 1023.0f) * 1024);
m_TempBatchMeshIDs = batchMeshIDs;
GPUDrivenProcessor.FetchMeshDatas(newMeshIDs, OnFetchMeshesDataForRegistration);
m_TempBatchMeshIDs = default;
newMeshIDs.Dispose();
batchMeshIDs.Dispose();
}
jobRanges.Dispose();
newMeshSet.Dispose();
}
private void RegisterMaterials(JaggedSpan<EntityId> materials)
{
using var _ = k_RegisterMaterials.Auto();
var jobRanges = JaggedJobRange.FromSpanWithMaxBatchSize(materials, FindNonRegisteredInstanceIDsJob<GPUDrivenMaterial>.MaxBatchSize, Allocator.TempJob);
var newMaterialIDSet = new NativeParallelHashSet<EntityId>(materials.totalLength, Allocator.TempJob);
new FindNonRegisteredInstanceIDsJob<GPUDrivenMaterial>
{
jobRanges = jobRanges.AsArray(),
jaggedInstanceIDs = materials,
hashMap = m_MaterialMap,
outInstanceIDWriter = newMaterialIDSet.AsParallelWriter()
}
.RunParallel(jobRanges);
if (!newMaterialIDSet.IsEmpty)
{
NativeArray<EntityId> newMaterialIDs = newMaterialIDSet.ToNativeArray(Allocator.TempJob);
var newMaterials = new NativeArray<GPUDrivenMaterial>(newMaterialIDs.Length, Allocator.TempJob);
GPUDrivenProcessor.RegisterMaterials(m_BRG, newMaterialIDs, newMaterials);
int totalMaterialsNum = m_MaterialMap.Count() + newMaterialIDs.Length;
m_MaterialMap.Capacity = Math.Max(m_MaterialMap.Capacity, Mathf.CeilToInt(totalMaterialsNum / 1023.0f) * 1024);
new RegisterNewMaterialsJob
{
instanceIDs = newMaterialIDs,
materials = newMaterials,
materialMap = m_MaterialMap.AsParallelWriter()
}
.RunParallel(newMaterialIDs.Length, 128);
newMaterialIDs.Dispose();
newMaterials.Dispose();
}
jobRanges.Dispose();
newMaterialIDSet.Dispose();
}
private void OnFetchMeshesDataForUpdate(NativeArray<EntityId> meshIDs,
NativeArray<GPUDrivenMeshData> meshDatas,
NativeArray<int> subMeshOffsets,
NativeArray<GPUDrivenSubMesh> subMeshBuffer)
{
Assert.IsTrue(meshIDs.Length == meshDatas.Length);
Assert.IsTrue(m_TempChangedMeshIDs.IsCreated);
InstanceCullingBatcherBurst.UpdateMeshData(meshIDs, meshDatas, subMeshOffsets, subMeshBuffer, ref m_MeshMap, ref m_TempChangedMeshIDs);
}
public NativeHashSet<EntityId> UpdateMeshData(NativeArray<EntityId> meshIDs, Allocator allocator)
{
NativeHashSet<EntityId> changeMeshIDs = new NativeHashSet<EntityId>(meshIDs.Length, allocator);
m_TempChangedMeshIDs = changeMeshIDs;
GPUDrivenProcessor.FetchMeshDatas(meshIDs, OnFetchMeshesDataForUpdate);
m_TempChangedMeshIDs = default;
return changeMeshIDs;
}
public NativeHashSet<EntityId> UpdateMaterialData(NativeArray<EntityId> materials, NativeArray<GPUDrivenMaterialData> materialDatas, Allocator allocator)
{
NativeHashSet<EntityId> changeMaterialIDs = new NativeHashSet<EntityId>(materials.Length, allocator);
InstanceCullingBatcherBurst.UpdateMaterialData(materials, materialDatas, ref m_MaterialMap, ref changeMaterialIDs);
return changeMaterialIDs;
}
public CPUDrawInstanceData GetDrawInstanceData()
{
return m_DrawInstanceData;
}
public unsafe JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext context, BatchCullingOutput cullingOutput, IntPtr userContext)
{
foreach (var batchID in m_BatchIDs)
{
if (batchID.Value == BatchID.Null)
return new JobHandle();
}
m_DrawInstanceData.RebuildDrawListsIfNeeded();
if (m_DrawInstanceData.drawRanges.Length == 0)
return default;
if (m_DrawInstanceData.drawBatches.Length == 0)
return default;
IncludeExcludeListFilter includeExcludeListFilter = default;
#if UNITY_EDITOR
includeExcludeListFilter = IncludeExcludeListFilter.GetFilterForCurrentCullingCallback(context, Allocator.TempJob);
// If inclusive filtering is enabled and we know there are no included entities,
// we can skip all the work because we know that the result will be nothing.
if (includeExcludeListFilter.IsIncludeEnabled && includeExcludeListFilter.IsIncludeEmpty)
{
includeExcludeListFilter.Dispose();
return default;
}
#else
includeExcludeListFilter = IncludeExcludeListFilter.GetEmptyFilter(Allocator.TempJob);
#endif
bool allowOcclusionCulling = m_InstanceDataSystem.hasBoundingSpheres;
JobHandle jobHandle;
using (k_CreateCullJobTree.Auto())
{
jobHandle = m_GRDContext.culler.CreateCullJobTree(
context,
cullingOutput,
m_InstanceDataSystem.renderWorld,
m_GRDContext.batcher.meshMap,
m_InstanceDataSystem.gpuBuffer.AsReadOnly(),
m_LODGroupDataSystem.lodGroupCullingData,
m_DrawInstanceData,
m_BatchIDs,
m_GRDContext.smallMeshScreenPercentage,
m_GRDContext.shadowSmallMeshScreenPercentages,
allowOcclusionCulling ? m_GRDContext.occlusionCullingCommon : null,
includeExcludeListFilter);
}
if (m_OnCompleteCallback != null)
m_OnCompleteCallback(jobHandle, context, cullingOutput);
return includeExcludeListFilter.Dispose(jobHandle);
}
public void DestroyDrawInstances(NativeArray<InstanceHandle> instances)
{
m_DrawInstanceData.DestroyDrawInstances(instances);
}
public void DestroyMaterials(NativeArray<EntityId> destroyedInstanceIDs)
{
if (destroyedInstanceIDs.Length == 0)
return;
using var _ = k_DestroyMaterials.Auto();
var destroyedBatchMaterials = new NativeList<uint>(destroyedInstanceIDs.Length, Allocator.TempJob);
foreach (EntityId instanceID in destroyedInstanceIDs)
{
if (m_MaterialMap.TryGetValue(instanceID, out GPUDrivenMaterial materialData))
{
BatchMaterialID batchMaterialID = materialData.materialID;
destroyedBatchMaterials.Add(batchMaterialID.value);
m_MaterialMap.Remove(instanceID);
m_BRG.UnregisterMaterial(batchMaterialID);
}
}
m_DrawInstanceData.DestroyMaterialDrawInstances(destroyedBatchMaterials.AsArray());
destroyedBatchMaterials.Dispose();
}
public void DestroyMeshes(NativeArray<EntityId> destroyedInstanceIDs)
{
if (destroyedInstanceIDs.Length == 0)
return;
using var _ = k_DestroyMeshes.Auto();
foreach (EntityId instanceID in destroyedInstanceIDs)
{
if (m_MeshMap.TryGetValue(instanceID, out var meshData))
{
meshData.subMeshes.Dispose();
m_MeshMap.Remove(instanceID);
m_BRG.UnregisterMesh(meshData.meshID);
}
}
}
public void BuildBatches(NativeArray<InstanceHandle> instances)
{
using var _ = k_BuildBatches.Auto();
DestroyDrawInstances(instances);
var rangeHash = m_DrawInstanceData.rangeHash;
var drawRanges = m_DrawInstanceData.drawRanges;
var batchHash = m_DrawInstanceData.batchHash;
var drawBatches = m_DrawInstanceData.drawBatches;
var drawInstances = m_DrawInstanceData.drawInstances;
InstanceCullingBatcherBurst.CreateDrawBatches(instances,
ref m_InstanceDataSystem.renderWorld,
m_MeshMap,
m_MaterialMap,
ref rangeHash,
ref drawRanges,
ref batchHash,
ref drawBatches,
ref drawInstances);
m_DrawInstanceData.NeedsRebuild();
}
public void RegisterAndBuildBatches(NativeArray<InstanceHandle> instances, in MeshRendererUpdateBatch updateBatch)
{
using var _ = k_RegisterAndBuildBatches.Auto();
// Wrap only the register portion so the recorder doesn't double-count BuildBatches.
using (k_RegisterMaterialsAndMeshes.Auto())
{
if (updateBatch.HasAnyComponent(MeshRendererComponentMask.Material))
RegisterMaterials(updateBatch.materialIDs);
if (updateBatch.HasAnyComponent(MeshRendererComponentMask.Mesh))
RegisterMeshes(updateBatch.meshIDs);
}
BuildBatches(instances);
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc861765f685b66449a8c8966e008804
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,270 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using Unity.Burst;
using UnityEngine.Assertions;
using Unity.Mathematics;
namespace UnityEngine.Rendering
{
[BurstCompile]
internal static class InstanceCullingBatcherBurst
{
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
public static void UpdateMaterialData(in NativeArray<EntityId> materialIDs,
in NativeArray<GPUDrivenMaterialData> materialDatas,
ref NativeParallelHashMap<EntityId, GPUDrivenMaterial> materialMap,
ref NativeHashSet<EntityId> changedMaterialIDs)
{
for (int index = 0; index < materialIDs.Length; index++)
{
EntityId materialID = materialIDs[index];
GPUDrivenMaterialData newMaterialData = materialDatas[index];
if (!materialMap.TryGetValue(materialID, out var material) || material.data.Equals(newMaterialData))
continue;
// Update the material data
material.data = newMaterialData;
materialMap[materialID] = material;
changedMaterialIDs.Add(materialID);
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
public static void UpdateMeshData(in NativeArray<EntityId> meshIDs,
in NativeArray<GPUDrivenMeshData> meshDatas,
in NativeArray<int> subMeshOffsets,
in NativeArray<GPUDrivenSubMesh> subMeshBuffer,
ref NativeParallelHashMap<EntityId, MeshInfo> meshMap,
ref NativeHashSet<EntityId> changedMeshIDs)
{
for (int index = 0; index < meshIDs.Length; index++)
{
EntityId meshID = meshIDs[index];
GPUDrivenMeshData newMeshData = meshDatas[index];
int subMeshOffset = subMeshOffsets[index];
int totalSubMeshCount = newMeshData.subMeshCount * math.max(newMeshData.meshLodCount, 1);
NativeArray<GPUDrivenSubMesh> newSubMeshes = subMeshBuffer.GetSubArray(subMeshOffset, totalSubMeshCount);
if (!meshMap.TryGetValue(meshID, out var meshInfo) || meshInfo.EqualsSourceData(newMeshData, newSubMeshes))
continue;
MeshInfo newMeshInfo = meshInfo;
newMeshInfo.meshLodCount = newMeshData.meshLodCount;
newMeshInfo.meshLodSelectionCurve = newMeshData.meshLodSelectionCurve;
newMeshInfo.subMeshes = new EmbeddedArray64<GPUDrivenSubMesh>(newSubMeshes, Allocator.Persistent);
// Update the mesh data
meshMap[meshID] = newMeshInfo;
changedMeshIDs.Add(meshID);
meshInfo.subMeshes.Dispose();
}
}
private static ref DrawRange EditDrawRange(in RangeKey key, ref NativeParallelHashMap<RangeKey, int> rangeHash, ref NativeList<DrawRange> drawRanges)
{
int drawRangeIndex;
if (!rangeHash.TryGetValue(key, out drawRangeIndex))
{
var drawRange = new DrawRange { key = key, drawCount = 0, drawOffset = 0 };
drawRangeIndex = drawRanges.Length;
rangeHash.Add(key, drawRangeIndex);
drawRanges.Add(drawRange);
}
ref DrawRange data = ref drawRanges.ElementAt(drawRangeIndex);
Assert.IsTrue(data.key.Equals(key));
return ref data;
}
private static ref DrawBatch EditDrawBatch(in DrawKey key,
in GPUDrivenSubMesh subMesh,
ref NativeParallelHashMap<DrawKey, int> batchHash,
ref NativeList<DrawBatch> drawBatches)
{
int drawBatchIndex;
if (!batchHash.TryGetValue(key, out drawBatchIndex))
{
var drawBatch = new DrawBatch
{
key = key,
instanceCount = 0,
instanceOffset = 0,
baseVertex = subMesh.baseVertex,
firstIndex = subMesh.indexStart,
indexCount = subMesh.indexCount,
topology = subMesh.topology,
};
drawBatchIndex = drawBatches.Length;
batchHash.Add(key, drawBatchIndex);
drawBatches.Add(drawBatch);
}
ref DrawBatch data = ref drawBatches.ElementAt(drawBatchIndex);
Assert.IsTrue(data.key.Equals(key));
return ref data;
}
private static void ProcessRenderer(InstanceHandle instance,
ref RenderWorld renderWorld,
in NativeParallelHashMap<EntityId, MeshInfo> meshMap,
in NativeParallelHashMap<EntityId, GPUDrivenMaterial> materialMap,
ref NativeParallelHashMap<RangeKey, int> rangeHash,
ref NativeList<DrawRange> drawRanges,
ref NativeParallelHashMap<DrawKey, int> batchHash,
ref NativeList<DrawBatch> drawBatches,
ref NativeList<DrawInstance> drawInstances)
{
Assert.IsTrue(instance.isValid, "Invalid Instance");
if (!instance.isValid)
return;
int instanceIndex = renderWorld.HandleToIndex(instance);
GPUArchetypeHandle archetype = renderWorld.gpuHandles[instanceIndex].archetype;
EntityId meshID = renderWorld.meshIDs[instanceIndex];
EntityId rendererID = renderWorld.instanceIDs[instanceIndex];
short lightmapIndex = renderWorld.lightmapIndices[instanceIndex];
InternalMeshRendererSettings rendererSettings = renderWorld.rendererSettings[instanceIndex];
int rendererPriority = renderWorld.rendererPriorities[instanceIndex];
ushort subMeshStartIndex = renderWorld.subMeshStartIndices[instanceIndex];
ushort staticBatchSubMeshCount = renderWorld.staticBatchSubMeshCounts[instanceIndex];
EmbeddedArray32<EntityId> subMaterialIDs = renderWorld.materialIDArrays[instanceIndex];
if (!meshMap.TryGetValue(meshID, out MeshInfo mesh))
return;
int lodLoopCount = math.max(mesh.meshLodCount, 1);
int availableSubMeshCount = mesh.subMeshes.Length / lodLoopCount - subMeshStartIndex;
int subMeshCount = rendererSettings.IsPartOfStaticBatch
? math.min(staticBatchSubMeshCount, availableSubMeshCount)
: availableSubMeshCount;
if (subMeshCount <= 0)
return;
int GetSubMeshIndexForMaterial(int materialIndex) => subMeshStartIndex + math.min(materialIndex, subMeshCount - 1);
// Scan all materials once to retrieve whether this renderer is indirect-compatible or not (and store it in the RangeKey).
// Also cache hash map lookups since we need them right after.
bool supportsIndirect = true;
bool isMetal = SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal;
var subMaterials = new NativeArray<GPUDrivenMaterial>(subMaterialIDs.Length, Allocator.Temp);
for (int i = 0; i < subMaterialIDs.Length; i++)
{
EntityId subMaterialID = subMaterialIDs[i];
if (!materialMap.TryGetValue(subMaterialID, out GPUDrivenMaterial subMaterial))
continue;
subMaterials[i] = subMaterial;
int subMeshIndex = GetSubMeshIndexForMaterial(i);
var subMesh = mesh.subMeshes[subMeshIndex * lodLoopCount];
// The indirect path does not support topology adjustment; use the direct path when this is required.
// Concretely, for quads, only use the indirect path if we allow quads natively (e.g. tessellation shaders).
supportsIndirect &= subMesh.topology != MeshTopology.Quads || subMaterial.hasTessellation;
supportsIndirect &= !isMetal || !subMaterial.hasTessellation;
}
var rangeKey = new RangeKey
{
layer = rendererSettings.ObjectLayer,
renderingLayerMask = rendererSettings.RenderingLayerMask,
motionMode = rendererSettings.MotionVectorGenerationMode,
shadowCastingMode = rendererSettings.ShadowCastingMode,
staticShadowCaster = rendererSettings.StaticShadowCaster,
rendererPriority = rendererPriority,
supportsIndirect = supportsIndirect
};
ref DrawRange drawRange = ref EditDrawRange(rangeKey, ref rangeHash, ref drawRanges);
for (int i = 0; i < subMaterials.Length; i++)
{
GPUDrivenMaterial subMaterial = subMaterials[i];
if (subMaterial.materialID == BatchMaterialID.Null)
continue;
// We always provide crossfade value packed in instance index. We don't use None even if there is no LOD to not split the batch.
var flags = BatchDrawCommandFlags.LODCrossFadeValuePacked;
if (LightmapUtils.UsesLightmaps(lightmapIndex))
flags |= BatchDrawCommandFlags.UseLegacyLightmapsKeyword;
// assume that a custom motion vectors pass contains deformation motion, so should always output motion vectors
// (otherwise this flag is set dynamically during culling only when the transform is changing)
if (subMaterial.isMotionVectorsPassEnabled)
flags |= BatchDrawCommandFlags.HasMotion;
if (subMaterial.isTransparent)
flags |= BatchDrawCommandFlags.HasSortingPosition;
if (subMaterial.supportsCrossFade)
flags |= BatchDrawCommandFlags.LODCrossFadeKeyword;
// Static batching uses per MeshRenderer sub-mesh offset
int subMeshIndex = GetSubMeshIndexForMaterial(i);
for (int lodLoopIndex = 0; lodLoopIndex < lodLoopCount; lodLoopIndex++)
{
GPUDrivenSubMesh subMesh = mesh.subMeshes[subMeshIndex * lodLoopCount + lodLoopIndex];
var drawKey = new DrawKey
{
materialID = subMaterial.materialID,
meshID = mesh.meshID,
submeshIndex = subMeshIndex,
activeMeshLod = mesh.isLodSelectionActive ? lodLoopIndex : -1,
flags = flags,
transparentInstanceID = subMaterial.isTransparent ? rendererID : EntityId.None,
range = rangeKey,
archetype = archetype,
// When we've opted out of lightmap texture arrays, we
// need to pass in a valid lightmap index. The engine
// uses this index for sorting and for breaking the
// batch when lightmaps change across draw calls, and
// for binding the correct light map.
lightmapIndex = lightmapIndex
};
ref DrawBatch drawBatch = ref EditDrawBatch(drawKey, subMesh, ref batchHash, ref drawBatches);
if (drawBatch.instanceCount == 0)
drawRange.drawCount += 1;
drawBatch.instanceCount += 1;
drawInstances.Add(new DrawInstance
{
key = drawKey,
instanceIndex = instance.index
});
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
public static void CreateDrawBatches(in NativeArray<InstanceHandle> instances,
ref RenderWorld renderWorld,
in NativeParallelHashMap<EntityId, MeshInfo> meshMap,
in NativeParallelHashMap<EntityId, GPUDrivenMaterial> materialMap,
ref NativeParallelHashMap<RangeKey, int> rangeHash,
ref NativeList<DrawRange> drawRanges,
ref NativeParallelHashMap<DrawKey, int> batchHash,
ref NativeList<DrawBatch> drawBatches,
ref NativeList<DrawInstance> drawInstances)
{
for (int i = 0; i < instances.Length; i++)
ProcessRenderer(instances[i], ref renderWorld, meshMap, materialMap, ref rangeHash, ref drawRanges, ref batchHash, ref drawBatches, ref drawInstances);
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a27d8a9d1dff4734cb0e88406d333941