Overview
MC-CPP implements several performance optimizations to maintain stable frame rates during gameplay, particularly during chunk streaming and world exploration.Chunk Loading FPS Stabilization
Problem Identification
The primary source of FPS drops during streaming chunk loading was identified inSave::load_chunk (src/save.cpp:89):
- Full lighting propagation: The function executed complete lighting queue processing via
world->propagate_skylight_increase(false, std::numeric_limits<int>::max())andworld->propagate_increase(false, std::numeric_limits<int>::max())without step limits in a single frame - Immediate mesh rebuilding: All subchunk meshes and the final chunk mesh were rebuilt immediately via
Subchunk::update_mesh()for all subchunks plusChunk::update_mesh(), bypassing the existing per-frame update system - Result: Even when streaming “one chunk per frame” (
Save::stream_next(1)in main.cpp), individual frames could hit heavyload_chunkoperations causing render time spikes
Solution: Eager Build Flag
Save::load_chunk now accepts a bool eager_build parameter (src/save.h:24, src/save.cpp:89):
Initial world loading (Save::load, src/save.cpp:221):
- Calls
load_chunk(chunk_pos, true)witheager_build = true - Preserves original lighting behavior: executes full lighting queue propagation
- Ensures the initial radius around the player is correctly lit immediately
Save::stream_next, src/save.cpp:330):
- Calls
load_chunk(pos, false)witheager_build = false - Distributes heavy operations across multiple frames
Deferred Processing (eager_build = false)
When streaming chunks during gameplay:
Lighting propagation:
- Removed unbounded calls to
propagate_skylight_increaseandpropagate_increase - Lighting queues are populated but processing happens in
World::tick()withOptions::LIGHT_STEPS_PER_TICKlimit (default: 2048 steps per tick) - Uses BFS limiting to constrain per-frame CPU time
- Removed immediate
Subchunk::update_mesh()loops andChunk::update_mesh()calls fromload_chunk - Instead calls
c->update_subchunk_meshes()which populates the chunk’s internalchunk_update_queue - Neighboring chunks also call
update_subchunk_meshes()viaupdate_neighborfor correct boundary updates - Heavy mesh building is deferred to later frames
Frame-Distributed Processing
Heavy operations are now distributed across frames inWorld::tick():
Lighting (src/world.cpp):
propagate_increase(true)andpropagate_skylight_increase(true)useOptions::LIGHT_STEPS_PER_TICK(default: 2048)- Limits BFS work per frame for block light and skylight
Chunk::process_chunk_updates()processes maxOptions::CHUNK_UPDATESsubchunks per tick (default: 4)World::chunk_building_queuecallsChunk::update_mesh()for at most one chunk per frame
Effect
When entering new world areas with chunk streaming:- Heavy work (lighting + mesh generation) no longer executes entirely in one
load_chunkcall - Work is spread across several subsequent frames
- FPS drops from CPU spikes are significantly reduced
- Trade-off: new chunks may “build up” visually over a few frames instead of appearing instantly
- Frame time becomes much more consistent
Performance Tunables
All performance options are defined insrc/options.h:
Chunk Processing
- Controls how many subchunk meshes are rebuilt per frame
- Lower values = smoother FPS but slower chunk appearance
- Higher values = faster chunk building but potential frame spikes
- Limits lighting propagation work per frame
- Affects both block light and skylight BFS traversal
- 2048 steps provides good balance for most scenarios
Render Distance
- Determines chunk loading radius around the player
- Used by
Save::load()andSave::update_streaming() - Directly impacts memory usage and visible chunk count
- Chunks beyond
(radius + 3)are automatically unloaded
Frame Limiting
- Synchronizes rendering with display refresh rate
- Reduces screen tearing but may cap framerate
- Controls CPU-GPU pipeline depth
- Lower values reduce input lag
- Higher values may improve throughput
- Additional frame time smoothing
- Helps reduce micro-stuttering
Visual Quality vs Performance
- Enables per-vertex ambient occlusion and smooth lighting
- Significant visual improvement with moderate performance cost
- Proper depth sorting for translucent blocks
- Disable for performance gain on older hardware
- RGB lighting instead of monochrome
- Minimal performance impact on modern GPUs
- Texture filtering mode
- Options:
GL_NEAREST,GL_LINEAR,GL_NEAREST_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_LINEAR - Affects texture quality at distance
- MSAA sample count (0, 2, 4, 8)
- Higher values = smoother edges but lower performance
Streaming Architecture
Initial Load (Save::load, src/save.cpp:221)
- Calculates center chunk from player position
- Generates offsets for all chunks within
RENDER_DISTANCE - Sorts by Manhattan distance for optimal loading order
- Loads chunks within
initial_radius(default: 2) immediately witheager_build = true - Queues remaining chunks in
pending_chunksfor streaming
Runtime Streaming (Save::update_streaming, src/save.cpp:266)
Called each frame to maintain chunk ring around player:
Queue generation:
- Checks when player moves to a new chunk
- Queues chunks within radius that don’t exist yet
- Avoids duplicates in
pending_chunks
- Unloads chunks beyond
(RENDER_DISTANCE + 3)squared distance - Saves modified chunks before unloading
- Unlinks neighbor pointers to prevent dangling references
- Removes from
visible_chunksandchunk_building_queue
Stream Processing (Save::stream_next, src/save.cpp:330)
Called from game loop to load pending chunks:
- Pops chunks from
pending_chunksqueue - Calls
load_chunk(pos, false)for deferred processing - Continues until queue empty or max chunks reached