In the PSone, Saturn and Nintendo 64 era, C was the dominant language. C compilers were in the toolchains of all platforms. The hardware APIs were in C.
Expectations, designs, team sizes, timescales and costs were all growing. It wasn’t feasible to rewrite a game for every platform, nor was it possible to hire huge numbers of programmers familiar with the hardware for every system.
C had long been established as efficient and portable, including to future systems. Hardware still differed, but it was now fast, similar and capable enough of abstracting game logic from platform concerns without significant cost. Assembly would still be used for performance-critical areas behind the abstraction layer.
Even certain C features tended to be avoided. Heap allocation was often outlawed after program initialisation due to performance/determinism concerns, and real numbers were represented in fixed point instead of floating point due to most platforms’ lack of a dedicated FPU.
The PS2, Xbox and GameCube era saw a shift to C++. Higher quality compilers were becoming available, with a desire to use language features to improve programmer productivity expression. Graduates were coming into the industry with C++ knowledge.
However, this move wasn’t greeted with open arms. Performance-critical areas like rendering and physics often remained in C, ported over from older codebases. Some resented wasting cycles on anything beyond the platform abstraction. Some said C++ obfuscated the compiled code. Some disliked the syntax. Some saw no need for new language features. Each concern had validity, but the shift was inevitable.
The amount of C++ used varied, but tended to be based around basic inheritance and virtual functions. Floating point was finally available to all, but some games still outlawed the use of heap allocation after start-up, requiring the use of arena-based allocations or fixed capacity containers. Virtual functions were used but the small caches available on PS2 meant penalties on every dynamic dispatch to load objects vptrs and vtables.
Experiments with templates began, tempered by fears of code bloat. Careful design with the kind of type aliasing tricks you would find in C (later to be formalised as ‘type erasure’) would mitigate some of that, but template (mis)use tended to produce the most friction in teams.
The arrival of multiprocessing
In the PS3, Xbox 360 era, C++ was prevalent. C still had its role, but middleware libraries tended to expose C++ APIs, and engines and games were in C++.
Few of C++’s features seemed to be off-limit – though exceptions and RTTI were still notably absent due to their runtime cost. Templates began to dominate codebases and compile and link times would suffer as a result, thanks to the compilation model inherited from C.
With multiprocessing now commonplace, work would get farmed off to other systems to run concurrently. This freed up the CPU to do more game logic in script languages for higher productivity, as the hardware was now capable of hosting embedded interpreters alongside its other work. Scripts could be reloaded on the fly, or at worst require a restart, rather than a slow build/link step.
Now, it seems nothing is taboo. The growth of powerful mobile devices and the flood of quality, cheap engines have seen games being written from scratch in all kinds of languages. C++’s CPU speed advantage is shifting towards compute shaders and the CPU becoming more of a task manager. Less game logic is being written in C++, the need to count bytes and cycles becoming increasingly redundant.
Today, and the future
So where does C/C++’s future lie? Engines will continue to strive for optimality. The C ABI has settled on being the de-facto for any new native system, and C/C++ compilers are available on every mainstream platform, allowing the creation of (mostly) portable code which runs natively without any runtime penalty.
C++ itself is moving in a different direction. Many languages are satisfied to express object-orientation in varying syntaxes but C++’s embrace of value semantics and generic programming is relatively unique, providing type-rich codebases where types encapsulate intent and programs do the best thing because the type system knows how components are assembled. It is also being positioned as giving the most performance per watt – important in a world increasingly concerned with reducing its power usage.
Now, if only they could fix those damn compile times...