One of the first things that ends up being written for a game is all the shell and platform logic: bringing up a window, routing input, constructing the game loop, starting threads, and setting up the graphics API. Since this code tends to be boilerplate-y and game developers tend to target multiple platforms, this is usually delegated to or abstracted away by libraries like SDL and GLFW.
I wanted to learn more about this “fundamental” layer since I wanted to implement a multi threaded game loop myself (one where simulation updates occur on a separate thread from graphics API draw calls), and I knew it was important to know the ins-and-outs when it came time to optimize for power and performance later on.
As a result, I’m sharing
AppleGfxBase, a template for creating Metal and OpenGL games for macOS, iOS, and tvOS in C/C++. The gist is that you would clone the above repo and then extend the example app only having to use C/C++ (and not Obj-C) to call into either Metal or OpenGL. It includes:
- A multi-threaded game loop, separating simulation updates from draw updates
- Windowing and display setup, synchronization to draw loop
- Entry points from
- Keyboard and mouse input capture
Specifically, the Apple recommended
CVDisplayLink (macOS) and
CADisplayLink (iOS/tvOS) are used to synchronize the display device’s refresh rate with the Metal or OpenGL draw loop. So that apps only need to write C++, this repo also leverages Apple’s Metal-cpp library, which shims and exposes the Obj-C Metal API to C++. It also adds its own extensions, to more easily support bring up of OpenGL and OpenGLES via C++.
In addition, the example app provides building blocks and example implementations for building an abstract “renderer” which uses either Metal, OpenGL (macOS), or OpenGLES (iOS/tvOS) depending on build flags. The abstractions include:
Camera, for perspective viewing
Transform, encapsulating the translate-rotate-scale matrix
Material, encapsulating shaders and shader parameters
Mesh, encapsulating primitive type, vertex attributes, and vertex buffers
MeshRenderer, pairing a Material to a Mesh
Surface, encapsulating the render output
These are all collected into a
Scene as EnTT components and entities, which the example app then quickly iterates over during the update and draw loops.
The repo uses Bazel to build, which is excellent for managing external dependencies, caching build intermediates perfectly, and extending with custom target types (e.g., generating and transforming files). Via the WORKSPACE file, glm, EnTT, and stb are pulled in at specific git commits. I’ve also written rules_metal, which provides the
metal_binary rule, allowing you to create Metal library targets in a similar way to Bazel’s native
cc_binary for C/C++. The beauty of this rule is that you can use C/C++ code in both the app and shader binaries to share definitions common to both, like vertex structs, uniform structs, and binding locations.
Finally, build and debugging integration with VSCode is provided via
task.json, which specify the target and flag combinations. This only works when targeting macOS, which VSCode is able to launch natively. For running on iOS/tvOS, Tulsi generates Xcode projects using Bazel, which can run on simulators or devices.
In the example app demo, you can rotate the spherical camera around the scene’s center with the left/right and up/down arrow keys. To zoom in/out, click the mouse’s left/right buttons. Finally, the scene will do a few animations to test modification of the simulation, like changing materials and transforms. Observe the 100s of cubes in the distance to give the camera a sense of depth and position.