From bcfaa44d66ea535be4c53be379a1527927b09fd9 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 12 Dec 2025 14:21:38 +0000 Subject: [PATCH] Amortize allocations when sorting renderables. Using OrderBy provides a stable sort, but internally allocates a buffer every time for storing the sort keys. We'd like to avoid this allocation, but we also can't directly use Array.Sort/Span.Sort as these are unstable sorts. By calculating sort keys with the item index embedded, we can ensure a stable sorting result whilst being able to reuse the buffer for the sort keys across future calls. --- OpenRA.Game/Graphics/WorldRenderer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 833c83b3b4..9539537ca7 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using OpenRA.Effects; using OpenRA.Primitives; using OpenRA.Traits; @@ -46,6 +47,7 @@ namespace OpenRA.Graphics readonly List renderablesBuffer = []; readonly IRenderer[] renderers; readonly IRenderPostProcessPass[] postProcessPasses; + long[] renderablesKeysBuffer = []; internal WorldRenderer(ModData modData, World world) { @@ -157,7 +159,14 @@ namespace OpenRA.Graphics renderablesBuffer.AddRange(e.Render(this)); // Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts - foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey)) + if (renderablesKeysBuffer.Length < renderablesBuffer.Count) + renderablesKeysBuffer = new long[Exts.NextPowerOf2(renderablesBuffer.Count)]; + for (var i = 0; i < renderablesBuffer.Count; i++) + renderablesKeysBuffer[i] = ((long)RenderableZPositionComparisonKey(renderablesBuffer[i]) << 32) + i; + var keys = renderablesKeysBuffer.AsSpan(0, renderablesBuffer.Count); + keys.Sort(CollectionsMarshal.AsSpan(renderablesBuffer)); + + foreach (var renderable in renderablesBuffer) preparedRenderables.Add(renderable.PrepareRender(this)); // PERF: Reuse collection to avoid allocations.