RTS

Overview#
| Team | Duration | Stack |
|---|---|---|
| 1 | Mar 2024 ~ | C++17, OpenGL |
A game engine focused on the RTS genre.
Problem Solving#
Memory Management#
Using the C++ standard library brought the following problems:
- High overhead from thousands to tens of thousands of malloc/free calls per frame
- Cache-line-exclusive cost (x86 LOCK) on every smart-pointer refcount update
- Performance variance from many failure points plus OS scheduler intervention
So the decision was to write a custom memory data structure from scratch.
Built a page-based Arena allocator with Windows
VirtualAlloc() — reserving memory a page at a time and handing back
addresses with nothing but stack-pointer movement.
Objects whose lifetime is exactly one frame are grouped together, and the allocator is reset wholesale at the start of every frame — constant-folding N calls → 1 allocation/deallocation call.
Implemented a Free List/Pool to reuse previously allocated blocks, cutting allocation/deallocation overhead.
Both rendering data processing and object management improved over malloc/free:
| µs/frame | malloc/free | Arena/Pool |
|---|---|---|
| Rendering | 106.12 | 14.88 |
| Object mgmt | 31.29 | 23.99 |
Rendering at Scale#
Starting from 100v100 and scaling battle size further, frame drops appeared.
Root-caused it with CPU and GPU profilers, then used a graphics debugger to confirm the geometry shader stage of the Shadow Pass had poor parallelism. Switched to plain instanced rendering, improving it 15ms → 9ms.

Mapped the GPU memory used for matrix updates on the CPU side for the lifetime of the game, removing Map/Unmap and fence-sync cost every frame. Compressed 4x4 → 3x4 matrices for upload, improving bandwidth too.
CPU animation updates have no cross-unit dependency, so they run through parallel for, minimizing synchronization cost too.
template <typename F>
void ParallelFor(Thread_Group& group, int64_t count, F&& func);

Achieved 60 FPS at 1000v1000.
| Before | After | |
|---|---|---|
| Draw calls | 10,000 | 30 |
| Shadow Pass | 15ms | 9ms |
| 600 vs 600 | 30 FPS | 100 FPS |
| 1000 vs 1000 | --- | 60 FPS |
Path Finding#
Path finding, essential to an RTS, needed a triangulation algorithm underneath it. Considered an open-source library, but ran into:
- No deletion operation, needed to punch out destroyed obstacles
- Heavy codebases built mainly for things like geological simulation
So the geometry library was written from scratch.
Implemented the algorithm as a pipeline:
- Constrained Delaunay Triangulation (CDT) generates the navmesh.
- A* computes the route.
- Simple Stupid Funnel simplifies it into a walkable path.
Faster build and runtime than well-known alternatives, plus deletion support:
| Own | CGAL | Triangle | |
|---|---|---|---|
| Build time (ms) | 748 | 17,805 | 3,894 |
| Deletion op | O | O | X |
| Runtime (ms) | 1.94 | 1.82 | 3.84 |
Open-sourced the code on GitHub; picked up 100+ stars.
