AppleGfxBase
Source Code on GitHub
Platform Shell
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
UIKit
orAppKit
- 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++.
Rendering Abstraction
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 viewingTransform
, encapsulating the translate-rotate-scale matrixMaterial
, encapsulating shaders and shader parametersMesh
, encapsulating primitive type, vertex attributes, and vertex buffersMeshRenderer
, pairing a Material to a MeshSurface
, 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.
Build Tooling
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 launch.json
and 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.
Demo
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.
Enjoy! 😃