Files
mrgameeng/src/MrGameEng.Graphics/RenderSystems.cs
T
Leonid Pershin fd6343bd09
CI / build-test (push) Failing after 1m8s
@
Add MrGameEng.AI utility-AI module; format codebase with CSharpier

New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction,
UtilityAi selector, Blackboard) plus CSharpier formatting applied across
the whole engine. Documents the CSharpier convention in CLAUDE.md.
@
2026-06-12 07:19:10 +03:00

132 lines
4.3 KiB
C#

using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
namespace MrGameEng.Graphics;
/// <summary>
/// First draw system: finds the active camera entity (the first one with a <see cref="Camera"/>
/// component) and begins the renderer frame. Without a camera entity a default camera showing
/// world origin at the top-left corner is used.
/// </summary>
public sealed class CameraSystem : QuerySystem<Camera>
{
private readonly Renderer2D _renderer;
/// <summary>Creates the system for <paramref name="renderer"/>.</summary>
public CameraSystem(Renderer2D renderer) => _renderer = renderer;
/// <inheritdoc />
protected override void OnUpdate()
{
foreach (var (cameras, _) in Query.Chunks)
{
if (cameras.Length > 0)
{
_renderer.BeginFrame(in cameras.Span[0]);
return;
}
}
_renderer.BeginFrameWithDefaultCamera();
}
}
/// <summary>
/// Submits every entity that has both <see cref="Sprite"/> and <see cref="Transform2D"/>.
/// Above <see cref="Renderer2DOptions.ParallelThreshold"/> 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.
/// </summary>
public sealed class SpriteRenderSystem : QuerySystem<Sprite, Transform2D>
{
private const int SegmentSize = 4096;
private readonly Renderer2D _renderer;
private readonly List<(Chunk<Sprite> Sprites, Chunk<Transform2D> Transforms)> _chunks = [];
private readonly List<(int Chunk, int Start, int Length)> _segments = [];
private int[] _segmentLengths = [];
/// <summary>Creates the system for <paramref name="renderer"/>.</summary>
public SpriteRenderSystem(Renderer2D renderer) => _renderer = renderer;
/// <inheritdoc />
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();
}
}
/// <summary>Last draw system: sorts the frame and issues the draw calls.</summary>
public sealed class RenderFlushSystem : BaseSystem
{
private readonly Renderer2D _renderer;
/// <summary>Creates the system for <paramref name="renderer"/>.</summary>
public RenderFlushSystem(Renderer2D renderer) => _renderer = renderer;
/// <inheritdoc />
protected override void OnUpdateGroup() => _renderer.EndFrame();
}