Skip to content

Instantly share code, notes, and snippets.

@ssell
Last active January 17, 2022 05:36
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save ssell/941d5f5c7a903033fa325a89c158d3f8 to your computer and use it in GitHub Desktop.
Save ssell/941d5f5c7a903033fa325a89c158d3f8 to your computer and use it in GitHub Desktop.
Implementation of a Unity ECS instanced Sprite renderer with basic frustum culling.
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;
}
}
}
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();
}
}
}
}
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);
}
}
}
}
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;
}
}
}
@Skybladev2
Copy link

Where do you get SpriteInstanceRendererComponent from?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment