perf: skip renderer for chunks with empty mesh
Pure-air chunks (above terrain) and chunks where every face culls produce a zero-length mesh from ChunkMesher.Build(). Previously InstallResult still allocated a real ChunkRenderer (VBO + VAO) and registered a zero-vertex draw call. With RenderDistanceVertical=2 and a player at surface Y, the topmost vertical layer is typically all pure air -- roughly H*H = ~289 wasted renderers per spawn. The chunk still gets added to _ready so SpawnReady, dirty-marking and unload bookkeeping continue to work; DrawAll's iteration over the renderer dictionary naturally skips entries that don't exist.
This commit is contained in:
@@ -35,6 +35,13 @@ internal sealed class FakeMesher : IChunkMesher
|
|||||||
=> new float[6]; // one vertex worth
|
=> new float[6]; // one vertex worth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mesher that returns an empty mesh, simulating a pure-air chunk.
|
||||||
|
internal sealed class EmptyMesher : IChunkMesher
|
||||||
|
{
|
||||||
|
public float[] Build(VoxelChunk chunk, BlockRegistry registry, Func<int, int, int, bool>? neighborOpaqueOutside = null)
|
||||||
|
=> Array.Empty<float>();
|
||||||
|
}
|
||||||
|
|
||||||
public class ChunkStreamerTests
|
public class ChunkStreamerTests
|
||||||
{
|
{
|
||||||
private static (ChunkStreamer streamer, World.World world, List<FakeRenderer> created) CreateStreamer(Configuration cfg)
|
private static (ChunkStreamer streamer, World.World world, List<FakeRenderer> created) CreateStreamer(Configuration cfg)
|
||||||
@@ -352,6 +359,38 @@ public class ChunkStreamerTests
|
|||||||
Assert.Equal(1, generateCalls);
|
Assert.Equal(1, generateCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InstallResult_EmptyMesh_SkipsRendererCreationButMarksReady()
|
||||||
|
{
|
||||||
|
var cfg = new Configuration
|
||||||
|
{
|
||||||
|
RenderDistanceHorizontal = 0,
|
||||||
|
RenderDistanceVertical = 0,
|
||||||
|
MaxGenerateJobsInFlight = 100,
|
||||||
|
MaxMeshUploadsPerFrame = 100,
|
||||||
|
};
|
||||||
|
var registry = new BlockRegistry();
|
||||||
|
var store = new InMemoryChunkModificationStore();
|
||||||
|
var world = new World.World(registry, store);
|
||||||
|
var mesher = new EmptyMesher();
|
||||||
|
var executor = new SynchronousJobExecutor();
|
||||||
|
int rendererFactoryCalls = 0;
|
||||||
|
Func<float[], IChunkRenderer> factory = m =>
|
||||||
|
{
|
||||||
|
rendererFactoryCalls++;
|
||||||
|
return new FakeRenderer(m);
|
||||||
|
};
|
||||||
|
Func<Vector3i, VoxelChunk> generate = _ => new VoxelChunk();
|
||||||
|
|
||||||
|
var streamer = new ChunkStreamer(cfg, world, generate, mesher, registry, executor, factory);
|
||||||
|
var pos = new Vector3i(0, 0, 0);
|
||||||
|
streamer.Tick(pos);
|
||||||
|
|
||||||
|
Assert.True(streamer.IsReady(pos));
|
||||||
|
Assert.Equal(0, rendererFactoryCalls);
|
||||||
|
Assert.Equal(0, streamer.RendererCount);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Tick_VerticalPriorityWeight_PrefersHorizontalAtSameDistance()
|
public void Tick_VerticalPriorityWeight_PrefersHorizontalAtSameDistance()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -210,8 +210,15 @@ public sealed class ChunkStreamer : IDisposable
|
|||||||
if (this._renderers.TryGetValue(result.Pos, out IChunkRenderer? old))
|
if (this._renderers.TryGetValue(result.Pos, out IChunkRenderer? old))
|
||||||
{
|
{
|
||||||
old.Dispose();
|
old.Dispose();
|
||||||
|
this._renderers.Remove(result.Pos);
|
||||||
}
|
}
|
||||||
|
// Skip renderer allocation for empty meshes (pure-air chunks above
|
||||||
|
// terrain, or chunks whose every face culls). The chunk still counts
|
||||||
|
// as ready — DrawAll's iteration over _renderers naturally no-ops.
|
||||||
|
if (result.Mesh.Length > 0)
|
||||||
|
{
|
||||||
this._renderers[result.Pos] = this._rendererFactory(result.Mesh);
|
this._renderers[result.Pos] = this._rendererFactory(result.Mesh);
|
||||||
|
}
|
||||||
this._ready.Add(result.Pos);
|
this._ready.Add(result.Pos);
|
||||||
this._dirty.Remove(result.Pos);
|
this._dirty.Remove(result.Pos);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user