Last active
April 6, 2022 20:44
-
-
Save AlexMerzlikin/01e220b942ca35bb023e0cff00cb7077 to your computer and use it in GitHub Desktop.
The first unoptimized version of BatchRenderGroup Boids Renderer for the blog post @ https://gamedev.center/trying-out-new-unity-api-batchrenderergroup/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using ThousandAnt.Boids; | |
using Unity.Collections; | |
using Unity.Collections.LowLevel.Unsafe; | |
using Unity.Jobs; | |
using Unity.Mathematics; | |
using UnityEngine; | |
using UnityEngine.Rendering; | |
using Random = UnityEngine.Random; | |
namespace BatchRendererGroupExample | |
{ | |
public unsafe class BatchRenderGroupBoidsRunner : Runner | |
{ | |
[SerializeField] private Mesh _mesh; | |
[SerializeField] private Material _material; | |
private PinnedMatrixArray _matrices; | |
private NativeArray<float> _noiseOffsets; | |
private float3* _centerFlock; | |
private JobHandle _boidsHandle; | |
private BatchRendererGroup _batchRendererGroup; | |
private GraphicsBuffer _gpuPersistentInstanceData; | |
private NativeArray<Vector4> _dataBuffer; | |
private BatchID _batchID; | |
private BatchMaterialID _materialID; | |
private BatchMeshID _meshID; | |
private bool _initialized; | |
private void Start() | |
{ | |
InitBoids(); | |
InitBatchRendererGroup(); | |
} | |
private void InitBatchRendererGroup() | |
{ | |
_batchRendererGroup = new BatchRendererGroup(OnPerformCulling, IntPtr.Zero); | |
var bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(1048576.0f, 1048576.0f, 1048576.0f)); | |
_batchRendererGroup.SetGlobalBounds(bounds); | |
if (_mesh) | |
{ | |
_meshID = _batchRendererGroup.RegisterMesh(_mesh); | |
} | |
if (_material) | |
{ | |
_materialID = _batchRendererGroup.RegisterMaterial(_material); | |
} | |
var objectToWorldID = Shader.PropertyToID("unity_ObjectToWorld"); | |
var worldToObjectID = Shader.PropertyToID("unity_WorldToObject"); | |
var bigDataBufferVector4Count = 4 + Size * (2 * 3); // 4xfloat4 zero + per instance = { 2x mat4x3 } | |
_dataBuffer = new NativeArray<Vector4>(bigDataBufferVector4Count, Allocator.Persistent); | |
_gpuPersistentInstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, bigDataBufferVector4Count * 4, 4); | |
// 64 bytes of zeroes, so loads from address 0 return zeroes. This is a BatchRendererGroup convention. | |
const int positionOffset = 4 * 4 * sizeof(float); | |
_dataBuffer[0] = new Vector4(0, 0, 0, 0); | |
_dataBuffer[1] = new Vector4(0, 0, 0, 0); | |
_dataBuffer[2] = new Vector4(0, 0, 0, 0); | |
_dataBuffer[3] = new Vector4(0, 0, 0, 0); | |
UpdatePositions(); | |
_gpuPersistentInstanceData.SetData(_dataBuffer); | |
var batchMetadata = | |
new NativeArray<MetadataValue>(2, Allocator.Temp, NativeArrayOptions.UninitializedMemory) | |
{ | |
[0] = CreateMetadataValue(objectToWorldID, positionOffset, true), | |
[1] = CreateMetadataValue(worldToObjectID, | |
positionOffset + Size * UnsafeUtility.SizeOf<float3x4>(), true), | |
}; | |
_batchID = _batchRendererGroup.AddBatch(batchMetadata, _gpuPersistentInstanceData.bufferHandle); | |
_initialized = true; | |
} | |
private void InitBoids() | |
{ | |
_matrices = new PinnedMatrixArray(Size); | |
_noiseOffsets = new NativeArray<float>(Size, Allocator.Persistent); | |
for (var i = 0; i < Size; i++) | |
{ | |
var currentTransform = transform; | |
var pos = currentTransform.position + Random.insideUnitSphere * Radius; | |
var rotation = Quaternion.Slerp(currentTransform.rotation, Random.rotation, 0.3f); | |
_noiseOffsets[i] = Random.value * 10f; | |
_matrices.Src[i] = Matrix4x4.TRS(pos, rotation, Vector3.one); | |
} | |
_centerFlock = (float3*) UnsafeUtility.Malloc( | |
UnsafeUtility.SizeOf<float3>(), | |
UnsafeUtility.AlignOf<float3>(), | |
Allocator.Persistent); | |
UnsafeUtility.MemSet(_centerFlock, 0, UnsafeUtility.SizeOf<float3>()); | |
} | |
private void Update() | |
{ | |
_boidsHandle.Complete(); | |
transform.position = *_centerFlock; | |
UpdatePositions(); | |
_gpuPersistentInstanceData.SetData(_dataBuffer); | |
var avgCenterJob = new BoidsPointerOnly.AverageCenterJob | |
{ | |
Matrices = _matrices.SrcPtr, | |
Center = _centerFlock, | |
Size = _matrices.Size | |
}.Schedule(); | |
var boidsJob = new BoidsPointerOnly.BatchedBoidJob | |
{ | |
Weights = Weights, | |
Goal = Destination.position, | |
NoiseOffsets = _noiseOffsets, | |
Time = Time.time, | |
DeltaTime = Time.deltaTime, | |
MaxDist = SeparationDistance, | |
Speed = MaxSpeed, | |
RotationSpeed = RotationSpeed, | |
Size = _matrices.Size, | |
Src = _matrices.SrcPtr, | |
Dst = _matrices.DstPtr, | |
}.Schedule(_matrices.Size, 32); | |
var combinedJob = JobHandle.CombineDependencies(boidsJob, avgCenterJob); | |
_boidsHandle = new BoidsPointerOnly.CopyMatrixJob | |
{ | |
Dst = _matrices.SrcPtr, | |
Src = _matrices.DstPtr | |
}.Schedule(_matrices.Size, 32, combinedJob); | |
} | |
private void UpdatePositions() | |
{ | |
const int positionOffset = 4; | |
var itemCountOffset = 3 * Size; // 3xfloat4 per matrix times amount of instances | |
for (var i = 0; i < Size; i++) | |
{ | |
{ | |
_dataBuffer[positionOffset + i * 3 + 0] = new Vector4(_matrices.Src[i].m00, _matrices.Src[i].m10, _matrices.Src[i].m20, _matrices.Src[i].m01); | |
_dataBuffer[positionOffset + i * 3 + 1] = new Vector4(_matrices.Src[i].m11, _matrices.Src[i].m21, _matrices.Src[i].m02, _matrices.Src[i].m12); | |
_dataBuffer[positionOffset + i * 3 + 2] = new Vector4(_matrices.Src[i].m22, _matrices.Src[i].m03, _matrices.Src[i].m13, _matrices.Src[i].m23); | |
var inverse = Matrix4x4.Inverse(_matrices.Src[i]); | |
_dataBuffer[positionOffset + i * 3 + 0 + itemCountOffset] = new Vector4(inverse.m00, inverse.m10, inverse.m20, inverse.m01); | |
_dataBuffer[positionOffset + i * 3 + 1 + itemCountOffset] = new Vector4(inverse.m11, inverse.m21, inverse.m02, inverse.m12); | |
_dataBuffer[positionOffset + i * 3 + 2 + itemCountOffset] = new Vector4(inverse.m22, inverse.m03, inverse.m13, inverse.m23); | |
} | |
} | |
} | |
private JobHandle OnPerformCulling( | |
BatchRendererGroup rendererGroup, | |
BatchCullingContext cullingContext, | |
BatchCullingOutput cullingOutput, | |
IntPtr userContext) | |
{ | |
if (!_initialized) | |
{ | |
return new JobHandle(); | |
} | |
var drawCommands = new BatchCullingOutputDrawCommands(); | |
drawCommands.drawRangeCount = 1; | |
drawCommands.drawRanges = Malloc<BatchDrawRange>(1); | |
drawCommands.drawRanges[0] = new BatchDrawRange | |
{ | |
drawCommandsBegin = 0, | |
drawCommandsCount = 1, | |
filterSettings = new BatchFilterSettings | |
{ | |
renderingLayerMask = 1, | |
layer = 0, | |
shadowCastingMode = ShadowCastingMode.On, | |
receiveShadows = true, | |
staticShadowCaster = false, | |
allDepthSorted = false | |
} | |
}; | |
drawCommands.visibleInstances = Malloc<int>(Size); | |
var n = 0; | |
for (var r = 0; r < Size; r++) | |
{ | |
drawCommands.visibleInstances[n++] = r; | |
} | |
drawCommands.visibleInstanceCount = n; | |
drawCommands.drawCommandCount = 1; | |
drawCommands.drawCommands = Malloc<BatchDrawCommand>(1); | |
drawCommands.drawCommands[0] = new BatchDrawCommand | |
{ | |
visibleOffset = 0, | |
visibleCount = (uint) n, | |
batchID = _batchID, | |
materialID = _materialID, | |
meshID = _meshID, | |
submeshIndex = 0, | |
splitVisibilityMask = 0xff, | |
sortingPosition = 0 | |
}; | |
drawCommands.instanceSortingPositions = null; | |
drawCommands.instanceSortingPositionFloatCount = 0; | |
cullingOutput.drawCommands[0] = drawCommands; | |
return new JobHandle(); | |
} | |
private static T* Malloc<T>(int count) where T : unmanaged | |
{ | |
return (T*) UnsafeUtility.Malloc( | |
UnsafeUtility.SizeOf<T>() * count, | |
UnsafeUtility.AlignOf<T>(), | |
Allocator.TempJob); | |
} | |
private static MetadataValue CreateMetadataValue(int nameID, int gpuAddress, bool isOverridden) | |
{ | |
const uint kIsOverriddenBit = 0x80000000; | |
return new MetadataValue | |
{ | |
NameID = nameID, | |
Value = (uint) gpuAddress | (isOverridden ? kIsOverriddenBit : 0), | |
}; | |
} | |
private void OnDisable() | |
{ | |
DisposeBoids(); | |
DisposeBatchRendererGroup(); | |
} | |
private void DisposeBoids() | |
{ | |
_boidsHandle.Complete(); | |
if (_noiseOffsets.IsCreated) | |
{ | |
_noiseOffsets.Dispose(); | |
} | |
if (_centerFlock != null) | |
{ | |
UnsafeUtility.Free(_centerFlock, Allocator.Persistent); | |
_centerFlock = null; | |
} | |
} | |
private void DisposeBatchRendererGroup() | |
{ | |
if (!_initialized) | |
{ | |
return; | |
} | |
_batchRendererGroup.RemoveBatch(_batchID); | |
if (_material) | |
{ | |
_batchRendererGroup.UnregisterMaterial(_materialID); | |
} | |
if (_mesh) | |
{ | |
_batchRendererGroup.UnregisterMesh(_meshID); | |
} | |
_batchRendererGroup.Dispose(); | |
_gpuPersistentInstanceData.Dispose(); | |
_dataBuffer.Dispose(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The optimized version is available at https://github.com/AlexMerzlikin/Unity-BatchRendererGroup-Boids/blob/master/Assets/Scripts/BatchRendererGroupExample/BatchRenderGroupBoidsRunner.cs