namespace MrGameEng.Graphics;
/// Compact identifier of a render layer. Obtained from .
public readonly record struct LayerId(byte Value)
{
/// The default layer (the first one registered).
public static readonly LayerId Default = new(0);
}
/// Coordinate space a layer is drawn in.
public enum LayerSpace
{
/// Drawn through the active camera's transform.
World,
/// Drawn in screen coordinates, ignoring the camera (HUD, UI). Never culled.
Screen,
}
/// How sprites are ordered within a layer.
public enum LayerSortMode
{
/// Order by the sprite's value (smaller = drawn first).
Depth,
///
/// Order by the entity's world Y position ():
/// top-down games, lower on screen = drawn in front. Place sprite origins at the
/// feet/base so the sort point matches the visual anchor.
///
YSort,
}
/// A registered render layer.
public sealed record RenderLayer(LayerId Id, string Name, LayerSpace Space, LayerSortMode SortMode);
///
/// Registry of render layers. Layers are registered up front (typically when the renderer is
/// created) and drawn in registration order. Maximum 256 layers. Reads are lock-free and
/// thread-safe (the renderer reads layers from parallel submit workers); registration swaps
/// an immutable snapshot, so registering mid-frame never tears a concurrent read.
///
public sealed class LayerRegistry
{
private readonly object _sync = new();
private volatile RenderLayer[] _layers = [];
/// Creates a registry containing the built-in "Default" world layer.
public LayerRegistry() => Register("Default");
/// Number of registered layers.
public int Count => _layers.Length;
/// Registers a layer drawn after all previously registered ones.
public LayerId Register(
string name,
LayerSpace space = LayerSpace.World,
LayerSortMode sortMode = LayerSortMode.Depth
)
{
lock (_sync)
{
var layers = _layers;
if (layers.Length == 256)
{
throw new InvalidOperationException(
"Maximum number of render layers (256) reached."
);
}
var id = new LayerId((byte)layers.Length);
var grown = new RenderLayer[layers.Length + 1];
Array.Copy(layers, grown, layers.Length);
grown[layers.Length] = new RenderLayer(id, name, space, sortMode);
_layers = grown;
return id;
}
}
/// Returns the layer with the given id; throws when the id was never registered.
public RenderLayer this[LayerId id]
{
get
{
var layers = _layers;
return id.Value < layers.Length
? layers[id.Value]
: throw new ArgumentOutOfRangeException(
nameof(id),
$"Render layer {id.Value} is not registered (registered: {layers.Length})."
);
}
}
}