Skip to content

Instantly share code, notes, and snippets.

@mdsitton
Created October 1, 2024 07:50
Show Gist options
  • Save mdsitton/6c8b3f706a9db21deb26832caa1f4e74 to your computer and use it in GitHub Desktop.
Save mdsitton/6c8b3f706a9db21deb26832caa1f4e74 to your computer and use it in GitHub Desktop.
Sprite renderer Overhead test
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
[DefaultExecutionOrder(-60)]
public class SpriteTextRenderer : Singleton<SpriteTextRenderer>
{
public struct TextInfo
{
public string text;
public SpriteFont font;
public Vector2Int position;
public Color32 color;
public int scale;
public bool bounce;
}
private int textCount = 0;
private TextInfo[] textLines = new TextInfo[100];
private SpritePool pool;
protected override void Awake()
{
base.Awake();
pool = SpritePool.Instance;
}
public int AddText(string text, SpriteFont font, Vector2Int position, Color32 color, int scale = 1, bool bounce = false)
{
if (font == null)
{
throw new ArgumentNullException("Font cannot be null");
}
if (disableNewText)
{
return -1;
}
if (textCount + 1 >= textLines.Length)
{
Array.Resize(ref textLines, textLines.Length * 2);
}
textLines[textCount] = new TextInfo
{
text = text,
font = font,
position = position,
color = color,
scale = scale,
bounce = bounce
}; ;
return textCount++;
}
public ref TextInfo UpdateText(int textId)
{
return ref textLines[textId];
}
public void ClearText()
{
textCount = 0;
}
bool disableNewText = false;
float timer = 0.0f;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RenderLine(ref TextInfo textInfo)
{
var willBounce = textInfo.bounce;
var text = textInfo.text;
var font = textInfo.font;
var textSpacing = font.charDistance * (willBounce ? bounce : 1.0f);
var position = textInfo.position;
var color = textInfo.color;
var scale = textInfo.scale;
var posx = position.x;
var posy = position.y;
Vector2 vector2 = new(posx, posy);
bool applyRandom = willBounce && timer > 0.1f;
char offset = applyRandom ? (char)UnityEngine.Random.Range(0, 20) : (char)0;
color.a = willBounce ? (byte)Mathf.Min((1.25f - bounce) * 255, 255) : (byte)255;
for (int i = 0; i < text.Length; i++)
{
var c = text[i];
var sprite = font.GetSprite((char)(c + offset));
ref var virtualSprite = ref pool.UseSprite();
if (virtualSprite.spriteIndex == SpritePool.SpritePoolSize - 1)
{
disableNewText = true;
break;
}
virtualSprite.sprite = sprite;
vector2.x = posx + (i * (textSpacing * scale));
virtualSprite.position = vector2;
virtualSprite.scale = new Vector2(scale, scale);
virtualSprite.color = color;
}
}
private float bounce;
private bool swap;
private float cycleTime = 0;
public void RenderText()
{
cycleTime += Time.deltaTime * 10;
if (cycleTime >= (2 * Mathf.PI))
{
cycleTime -= 2 * Mathf.PI;
swap = !swap;
}
bounce = swap ? 1.0f : 1.0f + Mathf.Sin(cycleTime) * 0.25f;
timer += Time.deltaTime;
for (int i = 0; i < textCount; i++)
{
RenderLine(ref textLines[i]);
}
if (timer > 0.2f)
{
timer -= 0.2f;
}
}
public void Update()
{
RenderText();
}
}
using System.Globalization;
using UnityEngine;
using System.Runtime.CompilerServices;
using UnityEngine;
public static class UtilExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValueEquals(this Color32 a, Color32 b) => a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
}
/// <summary>
/// A virtual sprite state which is used for reducing the
/// amount of changes on the sprite pool between frames
/// </summary>
public struct VirtualSpriteState
{
public Sprite sprite;
public Vector2 position;
public Vector2 scale;
public Color32 color;
public readonly int spriteIndex;
public bool enabled;
public VirtualSpriteState(int spriteIndex)
{
sprite = null;
position = Vector2.zero;
scale = Vector2.one;
color = Color.white;
this.spriteIndex = spriteIndex;
enabled = false;
}
public void Defaults()
{
sprite = null;
position = Vector2.zero;
scale = Vector2.one;
color = Color.white;
}
}
[DefaultExecutionOrder(-50)]
public class SpritePool : Singleton<SpritePool>
{
public SpriteRenderer[] spriteRenderers;
public VirtualSpriteState[] currentSpriteStates;
public VirtualSpriteState[] nextSpriteStates;
public const int SpritePoolSize = 5000;
public int currentSpriteIndex = 0;
private int textId;
public SpriteFont font;
protected override void Awake()
{
base.Awake();
FillPool();
font = Resources.Load<SpriteFont>("8x7 accent font");
textId = SpriteTextRenderer.Instance.AddText("Sprite count", font, Vector2Int.up, Color.magenta, 4);
}
public void FillPool()
{
spriteRenderers = new SpriteRenderer[SpritePoolSize];
nextSpriteStates = new VirtualSpriteState[SpritePoolSize];
currentSpriteStates = new VirtualSpriteState[SpritePoolSize];
for (int i = 0; i < SpritePoolSize; i++)
{
GameObject go = new("SpriteContainer");
go.transform.SetParent(transform);
spriteRenderers[i] = go.AddComponent<SpriteRenderer>();
spriteRenderers[i].enabled = false;
currentSpriteStates[i] = new VirtualSpriteState(i);
nextSpriteStates[i] = new VirtualSpriteState(i);
}
}
public ref VirtualSpriteState UseSprite()
{
if (currentSpriteIndex >= SpritePoolSize)
{
currentSpriteIndex--;
}
return ref nextSpriteStates[currentSpriteIndex++];
}
Color32 fullColor = Color.green;
private void Update()
{
ref var textInfo = ref SpriteTextRenderer.Instance.UpdateText(textId);
textInfo.text = $"Sprite count: {currentSpriteIndex.ToString(CultureInfo.InvariantCulture)}";
if (currentSpriteIndex >= SpritePoolSize)
{
textInfo.color = fullColor;
}
for (int i = 0; i < currentSpriteIndex; i++)
{
ref var newSpriteState = ref nextSpriteStates[i];
ref var oldSpriteState = ref currentSpriteStates[i];
if (!oldSpriteState.enabled)
{
newSpriteState.enabled = true;
spriteRenderers[i].enabled = newSpriteState.enabled;
}
if (newSpriteState.sprite != oldSpriteState.sprite)
{
spriteRenderers[i].sprite = newSpriteState.sprite;
}
if (newSpriteState.position.x != oldSpriteState.position.x || newSpriteState.position.y != oldSpriteState.position.y)
{
spriteRenderers[i].transform.position = newSpriteState.position;
}
if (newSpriteState.scale.x != oldSpriteState.scale.x || newSpriteState.scale.y != oldSpriteState.scale.y)
{
spriteRenderers[i].transform.localScale = newSpriteState.scale;
}
if (!newSpriteState.color.ValueEquals(oldSpriteState.color))
{
spriteRenderers[i].color = newSpriteState.color;
}
oldSpriteState = newSpriteState;
}
// Disable the rest of the sprites
for (int i = currentSpriteIndex; i < SpritePoolSize; i++)
{
spriteRenderers[i].enabled = false;
currentSpriteStates[i].enabled = false;
}
currentSpriteIndex = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment