Last active
January 17, 2022 05:36
-
-
Save ssell/941d5f5c7a903033fa325a89c158d3f8 to your computer and use it in GitHub Desktop.
Implementation of a Unity ECS instanced Sprite renderer with basic frustum culling.
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 Unity.Mathematics; | |
using Unity.Rendering; | |
using UnityEngine; | |
namespace Realms | |
{ | |
/// <summary> | |
/// Based on Unity.Rendering.FrustumPlanes since those aren't public for some reason. | |
/// </summary> | |
public struct FrustumPlanes | |
{ | |
public float4 Left; | |
public float4 Right; | |
public float4 Down; | |
public float4 Up; | |
public float4 Near; | |
public float4 Far; | |
public enum InsideResult | |
{ | |
Out, | |
In, | |
Partial | |
}; | |
public FrustumPlanes(Camera camera) | |
{ | |
Plane[] sourcePlanes = GeometryUtility.CalculateFrustumPlanes(camera); | |
Left = new float4(sourcePlanes[0].normal.x, sourcePlanes[0].normal.y, sourcePlanes[0].normal.z, sourcePlanes[0].distance); | |
Right = new float4(sourcePlanes[1].normal.x, sourcePlanes[1].normal.y, sourcePlanes[1].normal.z, sourcePlanes[1].distance); | |
Down = new float4(sourcePlanes[2].normal.x, sourcePlanes[2].normal.y, sourcePlanes[2].normal.z, sourcePlanes[2].distance); | |
Up = new float4(sourcePlanes[3].normal.x, sourcePlanes[3].normal.y, sourcePlanes[3].normal.z, sourcePlanes[3].distance); | |
Near = new float4(sourcePlanes[4].normal.x, sourcePlanes[4].normal.y, sourcePlanes[4].normal.z, sourcePlanes[4].distance); | |
Far = new float4(sourcePlanes[5].normal.x, sourcePlanes[5].normal.y, sourcePlanes[5].normal.z, sourcePlanes[5].distance); | |
} | |
public InsideResult Inside(Bounds bounds) | |
{ | |
var center = new float4(bounds.center.x, bounds.center.y, bounds.center.z, 1.0f); | |
var radius = math.sqrt(bounds.extents.x * bounds.extents.x + bounds.extents.y * bounds.extents.y); // A slightly oversized radius | |
var leftDistance = math.dot(Left, center); | |
var rightDistance = math.dot(Right, center); | |
var downDistance = math.dot(Down, center); | |
var upDistance = math.dot(Up, center); | |
var nearDistance = math.dot(Near, center); | |
var farDistance = math.dot(Far, center); | |
var leftOut = leftDistance < -radius; | |
var rightOut = rightDistance < -radius; | |
var downOut = downDistance < -radius; | |
var upOut = upDistance < -radius; | |
var nearOut = nearDistance < -radius; | |
var farOut = farDistance < -radius; | |
var anyOut = leftOut || rightOut || downOut || upOut || nearOut || farOut; | |
var leftIn = leftDistance > radius; | |
var rightIn = rightDistance > radius; | |
var downIn = downDistance > radius; | |
var upIn = upDistance > radius; | |
var nearIn = nearDistance > radius; | |
var farIn = farDistance > radius; | |
var allIn = leftIn && rightIn && downIn && upIn && nearIn && farIn; | |
if (anyOut) | |
{ | |
return InsideResult.Out; | |
} | |
if (allIn) | |
{ | |
return InsideResult.In; | |
} | |
return InsideResult.Partial; | |
} | |
} | |
} |
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 Unity.Burst; | |
using Unity.Collections; | |
using Unity.Collections.LowLevel.Unsafe; | |
using Unity.Entities; | |
using Unity.Jobs; | |
using Unity.Mathematics; | |
using Unity.Rendering; | |
using Unity.Transforms; | |
using UnityEngine; | |
namespace Realms | |
{ | |
public class SpriteInstanceCulling | |
{ | |
/// <summary> | |
/// Performs frustum culling on all provided chunks. | |
/// | |
/// If a sprite is determined to be inside of the frustum (completely or partially) | |
/// then it's VisibleLocalToWorld transform is updated to match the LocalToWorld matrix. | |
/// | |
/// A running count is kept of the number of visible entities in each chunk which | |
/// is then used in the rendering loop to determine if a chunk has any sprites to draw. | |
/// </summary> | |
[BurstCompile] | |
unsafe public struct FrustumCullingJob : IJobParallelFor | |
{ | |
[ReadOnly] public NativeArray<ArchetypeChunk> Chunks; | |
[ReadOnly] public NativeHashMap<int, Bounds> BoundsMap; // Key: Shared renderer index; Value: Bounds of the cached mesh that goes with the renderer. | |
[ReadOnly] public FrustumPlanes Planes; | |
[ReadOnly] public ArchetypeChunkComponentType<LocalToWorld> LocalToWorldType; | |
[ReadOnly] public ArchetypeChunkComponentType<Position> PositionType; | |
[ReadOnly] public ArchetypeChunkSharedComponentType<SpriteInstanceRendererComponent> SpriteRendererType; | |
public ArchetypeChunkComponentType<VisibleLocalToWorld> VisibleLocalToWorldType; | |
public NativeArray<int> ChunkVisibleCount; | |
public void Execute(int index) | |
{ | |
VisibleInFrustum(index); | |
} | |
void VisibleInFrustum(int index) | |
{ | |
var chunk = Chunks[index]; | |
var chunkEntityCount = chunk.Count; | |
var chunkVisibleCount = 0; | |
var rendererIndex = chunk.GetSharedComponentIndex(SpriteRendererType); | |
var entityPositions = chunk.GetNativeArray(PositionType); | |
float4x4* dstPtr = GetVisibleOutputBuffer(chunk); | |
float4x4* srcPtr = GetLocalToWorldSourceBuffer(chunk); | |
Bounds bounds; | |
if((dstPtr == null) || (srcPtr == null) || !BoundsMap.TryGetValue(rendererIndex, out bounds)) | |
{ | |
return; | |
} | |
for (int i = 0; i < chunkEntityCount; ++i) | |
{ | |
var position = entityPositions[i]; | |
Bounds entityBounds = bounds; | |
entityBounds.center += new Vector3(position.Value.x, position.Value.y, position.Value.z); | |
if(Planes.Inside(entityBounds) != FrustumPlanes.InsideResult.Out) | |
{ | |
UnsafeUtility.MemCpy(dstPtr + chunkVisibleCount, srcPtr + i, UnsafeUtility.SizeOf<float4x4>()); | |
chunkVisibleCount++; | |
} | |
} | |
ChunkVisibleCount[index] = chunkVisibleCount; | |
} | |
float4x4* GetLocalToWorldSourceBuffer(ArchetypeChunk chunk) | |
{ | |
var chunkLocalToWorld = chunk.GetNativeArray(LocalToWorldType); | |
if (chunkLocalToWorld.Length > 0) | |
{ | |
return (float4x4*)chunkLocalToWorld.GetUnsafeReadOnlyPtr(); | |
} | |
else | |
{ | |
return null; | |
} | |
} | |
float4x4* GetVisibleOutputBuffer(ArchetypeChunk chunk) | |
{ | |
var chunkVisibleLocalToWorld = chunk.GetNativeArray(VisibleLocalToWorldType); | |
return (float4x4*)chunkVisibleLocalToWorld.GetUnsafePtr(); | |
} | |
} | |
} | |
} |
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 System.Collections.Generic; | |
using Unity.Burst; | |
using Unity.Collections; | |
using Unity.Collections.LowLevel.Unsafe; | |
using Unity.Entities; | |
using Unity.Jobs; | |
using Unity.Mathematics; | |
using Unity.Rendering; | |
using Unity.Transforms; | |
using UnityEngine; | |
using UnityEngine.Assertions; | |
using UnityEngine.Experimental.PlayerLoop; | |
using UnityEngine.Profiling; | |
namespace Realms | |
{ | |
/// <summary> | |
/// Temporary implementation as there is not yet an official Unity version. | |
/// | |
/// This system directly makes use of ECS Chunks. As such, to properly understand how this | |
/// system works, please read the documentation on Chunk at: | |
/// | |
/// https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/reference/chunk_iteration.md | |
/// </summary> | |
[UpdateAfter(typeof(PreLateUpdate.ParticleSystemBeginUpdateAll))] | |
[ExecuteInEditMode] | |
public class SpriteInstanceRendererSystem : ComponentSystem | |
{ | |
public Camera ActiveCamera = null; | |
private Dictionary<SpriteInstanceRendererComponent, Material> _materialCache = new Dictionary<SpriteInstanceRendererComponent, Material>(); | |
private Dictionary<SpriteInstanceRendererComponent, Mesh> _meshCache = new Dictionary<SpriteInstanceRendererComponent, Mesh>(); | |
private NativeArray<ArchetypeChunk> _SpriteChunks; | |
private ComponentGroup _SpriteChunksQuery; | |
private FrustumPlanes _Planes; | |
private Matrix4x4[] _Matrices = new Matrix4x4[1023]; | |
private Vector4[] _Colors = new Vector4[1023]; | |
private int _LastLocalToWorldOrderVersion = -1; | |
/// <summary> | |
/// | |
/// </summary> | |
protected override void OnCreateManager() | |
{ | |
_SpriteChunksQuery = GetComponentGroup(new EntityArchetypeQuery | |
{ | |
Any = Array.Empty<ComponentType>(), | |
None = Array.Empty<ComponentType>(), | |
All = new ComponentType[] { typeof(SpriteInstanceRendererComponent), typeof(LocalToWorld), typeof(ColorComponent), typeof(MapTileComponent) } | |
}); | |
// Note the above requires a LocalToWorld component, but we never explicitly create one. | |
// That is OK, as LocalToWorld is automatically created, and added, by the internal TransformSystem. | |
// See: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/8f94d72d1fd9b8db896646d9d533055917dc265a/Documentation/reference/transform_system.md#updating-position-rotation-scale | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
protected override void OnDestroyManager() | |
{ | |
if (_SpriteChunks.IsCreated) | |
{ | |
_SpriteChunks.Dispose(); | |
} | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
protected override void OnUpdate() | |
{ | |
if (ActiveCamera == null) | |
{ | |
return; | |
} | |
_Planes = new FrustumPlanes(ActiveCamera); | |
UpdateMissingVisibleLocalToWorld(); | |
Profiler.BeginSample("UpdateSpriteChunkCache"); | |
UpdateSpriteChunkCache(); | |
Profiler.EndSample(); | |
Profiler.BeginSample("UpdateSpriteInstanceRenderer"); | |
UpdateSpriteInstanceRenderer(); | |
Profiler.EndSample(); | |
} | |
/// <summary> | |
/// Creates and adds a VisibleLocalToWorld component to all Entities that have a | |
/// sprite renderer and LocalToWorld transform, but do not yet have a VisibleLocalToWorld. | |
/// | |
/// The VisibleLocalToWorld is used as the 'output' of the Frustum culling in order to | |
/// designate which Entities are visible. | |
/// </summary> | |
void UpdateMissingVisibleLocalToWorld() | |
{ | |
var localToWorldOrderVersion = EntityManager.GetComponentOrderVersion<LocalToWorld>(); | |
if (localToWorldOrderVersion == _LastLocalToWorldOrderVersion) | |
{ | |
return; | |
} | |
EntityCommandBuffer entityCommandBuffer = new EntityCommandBuffer(Allocator.Temp); | |
var localToWorldQuery = new EntityArchetypeQuery | |
{ | |
Any = Array.Empty<ComponentType>(), | |
None = new ComponentType[] { typeof(VisibleLocalToWorld) }, | |
All = new ComponentType[] { typeof(SpriteInstanceRendererComponent), typeof(LocalToWorld) } | |
}; | |
var entityType = GetArchetypeChunkEntityType(); | |
var chunks = EntityManager.CreateArchetypeChunkArray(localToWorldQuery, Allocator.TempJob); | |
if (chunks.Length != 0) | |
{ | |
for (int i = 0; i < chunks.Length; ++i) | |
{ | |
var chunk = chunks[i]; | |
var entities = chunk.GetNativeArray(entityType); | |
for (int j = 0; j < chunk.Count; ++j) | |
{ | |
var entity = entities[j]; | |
entityCommandBuffer.AddComponent(entity, default(VisibleLocalToWorld)); | |
} | |
} | |
entityCommandBuffer.Playback(EntityManager); | |
} | |
entityCommandBuffer.Dispose(); | |
chunks.Dispose(); | |
_LastLocalToWorldOrderVersion = localToWorldOrderVersion; | |
} | |
/// <summary> | |
/// Fills the _SpriteChunks container. | |
/// </summary> | |
protected void UpdateSpriteChunkCache() | |
{ | |
// Could potentially optimize this function by checking if component version has updated, as is done in the MeshInstanceRendererSystem. | |
if (_SpriteChunks.IsCreated) | |
{ | |
_SpriteChunks.Dispose(); | |
} | |
var sharedComponentCount = EntityManager.GetSharedComponentCount(); | |
var spriteInstanceRendererType = GetArchetypeChunkSharedComponentType<SpriteInstanceRendererComponent>(); | |
var chunkRendererMap = new NativeMultiHashMap<int, int>(100000, Allocator.TempJob); | |
var chunks = _SpriteChunksQuery.CreateArchetypeChunkArray(Allocator.TempJob); | |
_SpriteChunks = new NativeArray<ArchetypeChunk>(chunks.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
// First we efficiently copy the chunks into the chunkRendererMap | |
var mapChunkRenderersJob = new MapChunkRenderers | |
{ | |
Chunks = chunks, | |
SpriteInstanceRendererType = spriteInstanceRendererType, | |
ChunkRendererMap = chunkRendererMap.ToConcurrent() | |
}; | |
var mapChunkRenderersJobHandle = mapChunkRenderersJob.Schedule(chunks.Length, 64); | |
// Next, we sort the gathered chunks and place them within _SpriteChunks. This job is dependent on the mapping job. | |
var gatherSortedChunksJob = new GatherSortedChunks | |
{ | |
ChunkRendererMap = chunkRendererMap, | |
SharedComponentCount = sharedComponentCount, | |
SortedChunks = _SpriteChunks, | |
Chunks = chunks | |
}; | |
var gatherSortedChunksJobHandle = gatherSortedChunksJob.Schedule(mapChunkRenderersJobHandle); | |
gatherSortedChunksJobHandle.Complete(); | |
// Clean up | |
chunkRendererMap.Dispose(); | |
chunks.Dispose(); | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
unsafe protected void UpdateSpriteInstanceRenderer() | |
{ | |
if (_SpriteChunks.Length == 0) | |
{ | |
return; | |
} | |
// Various types used in both the culling and rendering | |
var rendererType = GetArchetypeChunkSharedComponentType<SpriteInstanceRendererComponent>(); | |
var localToWorldType = GetArchetypeChunkComponentType<LocalToWorld>(false); | |
var visibleLocalToWorldType = GetArchetypeChunkComponentType<VisibleLocalToWorld>(false); | |
var positionType = GetArchetypeChunkComponentType<Position>(false); | |
var colorType = GetArchetypeChunkComponentType<ColorComponent>(true); | |
// Collects how many entities in each chunk are visible | |
var chunkVisibleCount = new NativeArray<int>(_SpriteChunks.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); | |
// Build the bounds info for frustum cullings | |
var boundsMap = BuildBoundsMap(rendererType); | |
// Perform the frustum culling | |
var frustumCullingJob = new SpriteInstanceCulling.FrustumCullingJob | |
{ | |
Chunks = _SpriteChunks, | |
BoundsMap = boundsMap, | |
Planes = _Planes, | |
LocalToWorldType = localToWorldType, | |
VisibleLocalToWorldType = visibleLocalToWorldType, | |
PositionType = positionType, | |
SpriteRendererType = rendererType, | |
ChunkVisibleCount = chunkVisibleCount | |
}; | |
// ... | |
var packedChunkIndices = new NativeArray<int>(_SpriteChunks.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); | |
var packedChunkCount = 0; | |
var packVisibleChunkIndicesJob = new PackVisibleChunkIndices | |
{ | |
Chunks = _SpriteChunks, | |
ChunkVisibleCount = chunkVisibleCount, | |
PackedChunkIndices = packedChunkIndices, | |
PackedChunkCount = &packedChunkCount | |
}; | |
var frustumCullingJobHandle = frustumCullingJob.Schedule(_SpriteChunks.Length, 64); | |
var packVisibleChunkIndicesJobHandle = packVisibleChunkIndicesJob.Schedule(frustumCullingJobHandle); | |
packVisibleChunkIndicesJobHandle.Complete(); | |
// Render all visible entities within each chunk | |
var lastRendererIndex = -1; | |
var batchCount = 0; | |
for (int i = 0; i < packedChunkCount; ++i) | |
{ | |
var chunkIndex = packedChunkIndices[i]; | |
var chunk = _SpriteChunks[chunkIndex]; | |
var activeCount = chunkVisibleCount[chunkIndex]; | |
var rendererIndex = chunk.GetSharedComponentIndex(rendererType); | |
if (rendererIndex == -1) | |
{ | |
continue; | |
} | |
var rendererChanged = rendererIndex != lastRendererIndex; | |
var fullBatch = ((batchCount + activeCount) > 1023); // Would the activeCount push our current batch size over the limit? | |
var visibleSpriteTransforms = chunk.GetNativeArray(visibleLocalToWorldType); | |
var spriteColors = chunk.GetNativeArray(colorType); | |
if ((fullBatch || rendererChanged) && (batchCount > 0)) | |
{ | |
RenderBatch(lastRendererIndex, batchCount); | |
batchCount = 0; | |
} | |
// Copy the activeCount items into the current batch stores | |
CopyToMatrix(visibleSpriteTransforms, activeCount, _Matrices, batchCount); | |
CopyToColor(spriteColors, activeCount, _Colors, batchCount); | |
batchCount += activeCount; | |
lastRendererIndex = rendererIndex; | |
} | |
// Did we exit the for-loop with some un-rendered items? | |
if (batchCount > 0) | |
{ | |
RenderBatch(lastRendererIndex, batchCount); | |
} | |
packedChunkIndices.Dispose(); | |
boundsMap.Dispose(); | |
chunkVisibleCount.Dispose(); | |
} | |
/// <summary> | |
/// Each sprite renderer uses a specific cached mesh. | |
/// Simply builds a map where the key is the renderer index and the value is the associated mesh's bounds. | |
/// </summary> | |
/// <param name="rendererType"></param> | |
/// <returns></returns> | |
private NativeHashMap<int, Bounds> BuildBoundsMap(ArchetypeChunkSharedComponentType<SpriteInstanceRendererComponent> rendererType) | |
{ | |
var boundsMap = new NativeHashMap<int, Bounds>(_SpriteChunks.Length, Allocator.TempJob); | |
for (int i = 0; i < _SpriteChunks.Length; ++i) | |
{ | |
var chunk = _SpriteChunks[i]; | |
var rendererIndex = chunk.GetSharedComponentIndex(rendererType); | |
var renderer = EntityManager.GetSharedComponentData<SpriteInstanceRendererComponent>(rendererIndex); | |
var mesh = GetOrCreateMesh(renderer); | |
boundsMap.TryAdd(rendererIndex, mesh.bounds); | |
} | |
return boundsMap; | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="rendererIndex"></param> | |
/// <param name="batchCount"></param> | |
protected void RenderBatch(int rendererIndex, int batchCount) | |
{ | |
var renderer = EntityManager.GetSharedComponentData<SpriteInstanceRendererComponent>(rendererIndex); | |
var mesh = GetOrCreateMesh(renderer); | |
var material = GetOrCreateMaterial(renderer); | |
MaterialPropertyBlock properties = new MaterialPropertyBlock(); | |
properties.SetVectorArray("_Color", _Colors); | |
Graphics.DrawMeshInstanced(mesh, 0, material, _Matrices, batchCount, properties); | |
} | |
/// <summary> | |
/// Attempts to retrieve the cached mesh for the specified renderer. | |
/// If no mesh is found, creates one. | |
/// </summary> | |
/// <param name="renderer"></param> | |
/// <returns></returns> | |
private Mesh GetOrCreateMesh(SpriteInstanceRendererComponent renderer) | |
{ | |
Mesh mesh; | |
if (!_meshCache.TryGetValue(renderer, out mesh)) | |
{ | |
Sprite sprite = renderer.Sprite; | |
float2 meshSize = new float2((sprite.rect.width / sprite.pixelsPerUnit), (sprite.rect.height / sprite.pixelsPerUnit)); | |
float2 meshPivot = new float2((sprite.pivot.x / sprite.rect.width * meshSize.x), (sprite.pivot.y / sprite.rect.height * meshSize.y)); | |
mesh = CreateQuad(meshSize, meshPivot); | |
_meshCache.Add(renderer, mesh); | |
} | |
return mesh; | |
} | |
/// <summary> | |
/// Attempts to retrieve the cached material for the specified renderer. | |
/// If no material is found, creates one. | |
/// </summary> | |
/// <param name="renderer"></param> | |
/// <returns></returns> | |
private Material GetOrCreateMaterial(SpriteInstanceRendererComponent renderer) | |
{ | |
Material material; | |
if (!_materialCache.TryGetValue(renderer, out material)) | |
{ | |
Sprite sprite = renderer.Sprite; | |
material = new Material(Shader.Find("ECS/Sprite")) | |
{ | |
enableInstancing = true, | |
mainTexture = sprite.texture | |
}; | |
float4 rect = new float4((sprite.rect.x / sprite.texture.width), | |
(sprite.rect.y / sprite.texture.height), | |
(sprite.rect.width / sprite.texture.width), | |
(sprite.rect.height / sprite.texture.height)); | |
// Set the _Rect vector used for accessing indices within a sprite sheet | |
material.SetVector("_Rect", rect); | |
_materialCache.Add(renderer, material); | |
} | |
return material; | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="size"></param> | |
/// <param name="pivot"></param> | |
/// <returns></returns> | |
private Mesh CreateQuad(float2 size, float2 pivot) | |
{ | |
return new Mesh | |
{ | |
vertices = new Vector3[4] | |
{ | |
new Vector3(0.0f, 0.0f, 0.0f), | |
new Vector3(1.0f, 0.0f, 0.0f), | |
new Vector3(1.0f, 1.0f, 0.0f), | |
new Vector3(0.0f, 1.0f, 0.0f) | |
}, | |
uv = new Vector2[4] | |
{ | |
new Vector2(0.0f, 0.0f), | |
new Vector2(1.0f, 0.0f), | |
new Vector2(1.0f, 1.0f), | |
new Vector2(0.0f, 1.0f) | |
}, | |
triangles = new int[6] | |
{ | |
0, 1, 2, | |
2, 3, 0 | |
} | |
}; | |
} | |
/// <summary> | |
/// Builds up the ChunkRendererMap which maps a chunk index with a shared renderer component index. | |
/// Based on the MeshInstanceRendererSystem's MapChunkRenderers. | |
/// </summary> | |
[BurstCompile] | |
struct MapChunkRenderers : IJobParallelFor | |
{ | |
[ReadOnly] public NativeArray<ArchetypeChunk> Chunks; | |
[ReadOnly] public ArchetypeChunkSharedComponentType<SpriteInstanceRendererComponent> SpriteInstanceRendererType; | |
public NativeMultiHashMap<int, int>.Concurrent ChunkRendererMap; | |
public void Execute(int index) | |
{ | |
var chunk = Chunks[index]; | |
var rendererSharedComponentIndex = chunk.GetSharedComponentIndex(SpriteInstanceRendererType); | |
ChunkRendererMap.Add(rendererSharedComponentIndex, index); | |
} | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
[BurstCompile] | |
struct GatherSortedChunks : IJob | |
{ | |
[ReadOnly] public NativeMultiHashMap<int, int> ChunkRendererMap; | |
public int SharedComponentCount; | |
public NativeArray<ArchetypeChunk> SortedChunks; | |
public NativeArray<ArchetypeChunk> Chunks; | |
public void Execute() | |
{ | |
int sortedIndex = 0; | |
for (int i = 0; i < SharedComponentCount; i++) | |
{ | |
int chunkIndex = 0; | |
NativeMultiHashMapIterator<int> it; | |
if (ChunkRendererMap.TryGetFirstValue(i, out chunkIndex, out it)) | |
{ | |
do | |
{ | |
SortedChunks[sortedIndex++] = Chunks[chunkIndex]; | |
} while (ChunkRendererMap.TryGetNextValue(out chunkIndex, ref it)); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Produces a final array of only visible entities. | |
/// </summary> | |
[BurstCompile] | |
unsafe struct PackVisibleChunkIndices : IJob | |
{ | |
[ReadOnly] public NativeArray<ArchetypeChunk> Chunks; | |
[ReadOnly] public NativeArray<int> ChunkVisibleCount; | |
public NativeArray<int> PackedChunkIndices; | |
[NativeDisableUnsafePtrRestriction] public int* PackedChunkCount; | |
public void Execute() | |
{ | |
var packedChunkCount = 0; | |
for (int i = 0; i < Chunks.Length; ++i) | |
{ | |
if (ChunkVisibleCount[i] > 0) | |
{ | |
PackedChunkIndices[packedChunkCount] = i; | |
packedChunkCount++; | |
} | |
*PackedChunkCount = packedChunkCount; | |
} | |
} | |
} | |
/// <summary> | |
/// Stolen from MeshInstanceRendererSystem since they don't make it public for whatever reason. | |
/// </summary> | |
/// <param name="transforms"></param> | |
/// <param name="count"></param> | |
/// <param name="outMatrices"></param> | |
/// <param name="offset"></param> | |
static unsafe void CopyToMatrix(NativeSlice<VisibleLocalToWorld> transforms, int count, Matrix4x4[] outMatrices, int offset) | |
{ | |
// @TODO: This is using unsafe code because the Unity DrawInstances API takes a Matrix4x4[] instead of NativeArray. | |
Assert.AreEqual(sizeof(Matrix4x4), sizeof(VisibleLocalToWorld)); | |
fixed (Matrix4x4* resultMatrices = outMatrices) | |
{ | |
VisibleLocalToWorld* sourceMatrices = (VisibleLocalToWorld*)transforms.GetUnsafeReadOnlyPtr(); | |
UnsafeUtility.MemCpy(resultMatrices + offset, sourceMatrices, UnsafeUtility.SizeOf<Matrix4x4>() * count); | |
} | |
} | |
/// <summary> | |
/// Based on the above, but modified to copy Colors. | |
/// </summary> | |
/// <param name="colors"></param> | |
/// <param name="count"></param> | |
/// <param name="outColors"></param> | |
/// <param name="offset"></param> | |
static unsafe void CopyToColor(NativeSlice<ColorComponent> colors, int count, Vector4[] outColors, int offset) | |
{ | |
Assert.AreEqual(sizeof(ColorComponent), sizeof(Vector4)); | |
fixed (Vector4* resultColors = outColors) | |
{ | |
ColorComponent* sourceColors = (ColorComponent*)colors.GetUnsafeReadOnlyPtr(); | |
UnsafeUtility.MemCpy(resultColors + offset, sourceColors, UnsafeUtility.SizeOf<Vector4>() * count); | |
} | |
} | |
} | |
} |
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 Unity.Entities; | |
using UnityEngine; | |
using UnityEngine.Experimental.Rendering; | |
namespace Realms | |
{ | |
/// <summary> | |
/// Based on Unity.Rendering.RenderingSystemBootstrap. | |
/// Sets the active camera for the sprite renderer so it can perform frustum culling. | |
/// </summary> | |
[ExecuteInEditMode] | |
public class RenderingSystemBootstrap : ComponentSystem | |
{ | |
protected override void OnCreateManager() | |
{ | |
RenderPipeline.beginCameraRendering += OnBeforeCull; | |
Camera.onPreCull += OnBeforeCull; | |
} | |
protected override void OnUpdate() | |
{ | |
} | |
[Inject] | |
#pragma warning disable 649 | |
SpriteInstanceRendererSystem m_SpriteRendererSystem; | |
public void OnBeforeCull(Camera camera) | |
{ | |
#if UNITY_EDITOR && UNITY_2018_3_OR_NEWER | |
var prefabEditMode = UnityEditor.SceneManagement.StageUtility.GetCurrentStageHandle() != | |
UnityEditor.SceneManagement.StageUtility.GetMainStageHandle(); | |
var gameCamera = (camera.hideFlags & HideFlags.DontSave) == 0; | |
if (prefabEditMode && !gameCamera) | |
return; | |
#endif | |
m_SpriteRendererSystem.ActiveCamera = camera; | |
m_SpriteRendererSystem.Update(); | |
m_SpriteRendererSystem.ActiveCamera = null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Where do you get SpriteInstanceRendererComponent from?