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})." ); } } }