Parallel rendering pipeline, ring vertex buffer, phase timings

Five optimizations measured on the 100k-entity stress scene (Release,
vsync off): 103 FPS baseline -> 297 FPS.

- Sprite submission and vertex building run on all cores above
  Renderer2DOptions.ParallelThreshold (default 8192). Work is sliced
  into 4096-entity segments: a Friflo chunk holds a whole archetype,
  so per-chunk parallelism degenerates to one thread. Segments merge
  in deterministic order, preserving radix sort stability.
- Vertex buffer is ring-written with SetDataOptions.NoOverwrite
  (GPU buffer 2x frame size); Discard only on wrap-around.
- Texture2DRegion precomputes UVs - four float divisions per sprite
  per frame removed.
- Renderer2D exposes per-phase timings (submit/sort/build/upload/draw),
  shown in the sample HUD - all further optimization is data-driven.
- Sample BounceSystem parallelized the same segmented way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-11 05:06:55 +03:00
co-authored by Claude Fable 5
parent af319d1276
commit e06f24a319
10 changed files with 589 additions and 132 deletions
@@ -99,6 +99,53 @@ public class SpriteBatcherTests
}
}
[Fact]
public void ChunkedSubmit_MergesInChunkOrder_AndCountsCulled()
{
var batcher = new SpriteBatcher();
var key = SpriteSortKey.Make(0, 0f, 0); // одинаковый ключ — порядок задаётся слиянием чанков
batcher.BeginChunks([3, 2]);
var writer1 = batcher.GetChunkWriter(1); // чанки могут заполняться в любом порядке (параллельно)
writer1.Add(Instance(10), key);
writer1.AddCulled();
batcher.EndChunk(1, in writer1);
var writer0 = batcher.GetChunkWriter(0);
writer0.Add(Instance(1), key);
writer0.Add(Instance(2), key);
batcher.EndChunk(0, in writer0);
var accepted = batcher.CommitChunks();
Assert.Equal(3, accepted);
Assert.Equal(1, batcher.LastChunkCulled);
var order = batcher.Sort();
Assert.Equal(1, batcher[order[0]].Layer); // сначала чанк 0...
Assert.Equal(2, batcher[order[1]].Layer);
Assert.Equal(10, batcher[order[2]].Layer); // ...затем чанк 1 — стабильно
}
[Fact]
public void ChunkedSubmit_GrowsMainArrays_WhenNeeded()
{
var batcher = new SpriteBatcher(initialCapacity: 2);
batcher.BeginChunks([10]);
var writer = batcher.GetChunkWriter(0);
for (byte i = 0; i < 10; i++)
{
writer.Add(Instance(i), i);
}
batcher.EndChunk(0, in writer);
Assert.Equal(10, batcher.CommitChunks());
Assert.Equal(10, batcher.Count);
Assert.Equal(0, batcher[batcher.Sort()[0]].Layer);
}
[Fact]
public void Clear_ResetsCount_KeepsWorking()
{