RTS

https://github.com/raylee9919/rts


GitHub YouTube

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.

memory_1

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.

memory_2

Implemented a Free List/Pool to reuse previously allocated blocks, cutting allocation/deallocation overhead.

memory_3

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.

mass_1

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.

mass_3

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.

mass_2

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);

mass_2

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:

  1. Constrained Delaunay Triangulation (CDT) generates the navmesh.
  2. A* computes the route.
  3. Simple Stupid Funnel simplifies it into a walkable path.

navmesh_2

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.

navmesh_1