Optimizing Performance for Thrive: Heavy Lies the Crown

A medieval city builder with RTS elements — a custom GPU-instanced vegetation system, compute shader consolidation, and the Burst compiler to hit low-spec targets.

Studio Zugalu Entertainment
Engine Unity
Category Optimization
Source Unity
Optimizing Performance for Thrive: Heavy Lies the Crown

Overview

Zugalu Entertainment's medieval city builder with RTS elements was CPU-bottlenecked by its vegetation system. A previous third-party solution cost approximately 3ms of CPU time per frame — significant enough to block the game's ~10 million tile map from running at acceptable framerates.

Zugalu built a custom GPU-driven vegetation system using compute shaders and GPU instancing, eliminating the bottleneck entirely. The system renders hundreds of thousands of instances with per-tile masking at a fraction of the original cost.

Curated by Feative Studios
Optimizing Performance for Thrive: Heavy Lies the Crown

Challenge

The game spawns hundreds of thousands of vegetation instances across a 10-million-tile map. When the camera moves at high speed during RTS gameplay, the CPU must determine which tiles are visible, which vegetation types each tile needs, and cull everything outside the viewport — all before the GPU can draw a single frame.

Per-tile masking (e.g., roads suppressing grass on adjacent tiles) was prohibitively expensive on the CPU side. Sending 10 million tiles of data from CPU to GPU every frame was equally unworkable. The previous solution's 3ms CPU cost left almost no headroom for gameplay logic, pathfinding, or AI.

Solution

Compute shaders with groupshared memory and Interlocked operations handle thread-to-thread communication within a dispatch, enabling the GPU itself to resolve per-tile masking without round-tripping through the CPU. Instance data is packed and the indirect draw buffer is updated in a single compute dispatch.

The team consolidated 50 separate SetData() calls into a single buffer with offset indexing, eliminating 49 redundant GPU synchronization points per frame. Unity's Job System combined with the Burst Compiler extracts and prepares tile data in parallel — the Burst Compiler cut this hot path from over 1ms to under 0.3ms.

Since the terrain is non-deformable, terrain slope data is pre-processed at startup and stored alongside tile indices, avoiding per-frame recalculation. Perlin and Simplex noise for vegetation variation keeps each compute thread independent, maximizing parallelism across the GPU.

Result

The custom vegetation system runs in 0.5ms — an 83% reduction from the original 3ms. The Burst Compiler alone cut tile data transfer from over 1ms to under 0.3ms. Consolidating the SetData() calls saved an additional 0.1ms of CPU time, and the GPU-side compute approach eliminated the per-tile masking bottleneck entirely.

Key Takeaways

Takeaway 1Consolidating 50 SetData() calls into a single buffer with offset indexing accounted for 20% of total CPU savings — a straightforward change with outsized impact.
Takeaway 2Pre-processing terrain slope at startup is free when the terrain is static — never recalculate what doesn't change.
Takeaway 3Using Perlin/Simplex noise for vegetation variation keeps each compute thread independent, maximizing GPU parallelism without shared state.

Original Study

Source Optimizing Performance for Thrive: Heavy Lies the Crown

A Unity blog interview with Zugalu Entertainment's CTO and technical artist.

View the Original Study
Keep Exploring

Want results like this?

Whether it is a full product or targeted engineering support, Feative Studios plugs into your pipeline with no friction.