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 or AppKit
  • 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 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.

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! 😃