using Friflo.Engine.ECS; using Friflo.Engine.ECS.Systems; namespace MrGameEng.Graphics; /// /// First draw system: finds the active camera entity (the first one with a /// component) and begins the renderer frame. Without a camera entity a default camera showing /// world origin at the top-left corner is used. /// public sealed class CameraSystem : QuerySystem { private readonly Renderer2D _renderer; /// Creates the system for . public CameraSystem(Renderer2D renderer) => _renderer = renderer; /// protected override void OnUpdate() { foreach (var (cameras, _) in Query.Chunks) { if (cameras.Length > 0) { _renderer.BeginFrame(in cameras.Span[0]); return; } } _renderer.BeginFrameWithDefaultCamera(); } } /// /// Submits every entity that has both and . /// Above entities, work is split into /// fixed-size segments processed on all cores (a Friflo chunk holds a whole archetype, so /// chunks themselves are too coarse); results merge in segment order, preserving sort stability. /// public sealed class SpriteRenderSystem : QuerySystem { private const int SegmentSize = 4096; private readonly Renderer2D _renderer; private readonly List<(Chunk Sprites, Chunk Transforms)> _chunks = []; private readonly List<(int Chunk, int Start, int Length)> _segments = []; private int[] _segmentLengths = []; /// Creates the system for . public SpriteRenderSystem(Renderer2D renderer) => _renderer = renderer; /// protected override void OnUpdate() { _chunks.Clear(); var total = 0; foreach (var (sprites, transforms, _) in Query.Chunks) { _chunks.Add((sprites, transforms)); total += sprites.Length; } if (total < _renderer.ParallelThreshold) { foreach (var (sprites, transforms) in _chunks) { var s = sprites.Span; var t = transforms.Span; for (var i = 0; i < s.Length; i++) { _renderer.Submit(in t[i], in s[i]); } } return; } _segments.Clear(); for (var c = 0; c < _chunks.Count; c++) { var length = _chunks[c].Sprites.Length; for (var start = 0; start < length; start += SegmentSize) { _segments.Add((c, start, Math.Min(SegmentSize, length - start))); } } if (_segmentLengths.Length < _segments.Count) { Array.Resize(ref _segmentLengths, _segments.Count); } for (var i = 0; i < _segments.Count; i++) { _segmentLengths[i] = _segments[i].Length; } _renderer.BeginChunkedSubmit(_segmentLengths.AsSpan(0, _segments.Count)); Parallel.For( 0, _segments.Count, segmentIndex => { var (chunk, start, length) = _segments[segmentIndex]; var (sprites, transforms) = _chunks[chunk]; var writer = _renderer.GetChunkWriter(segmentIndex); var s = sprites.Span.Slice(start, length); var t = transforms.Span.Slice(start, length); for (var i = 0; i < s.Length; i++) { _renderer.SubmitInto(ref writer, in t[i], in s[i]); } _renderer.EndChunk(segmentIndex, in writer); } ); _renderer.CommitChunkedSubmit(); } } /// Last draw system: sorts the frame and issues the draw calls. public sealed class RenderFlushSystem : BaseSystem { private readonly Renderer2D _renderer; /// Creates the system for . public RenderFlushSystem(Renderer2D renderer) => _renderer = renderer; /// protected override void OnUpdateGroup() => _renderer.EndFrame(); }