-
-
Notifications
You must be signed in to change notification settings - Fork 28
Technical design
Want to peek under the hood of Hotham and see how it's working? Well come on in and get your hands dirty!
In sum, Hotham aims to be laser focussed on the goal of being a high performance, easy to use game engine for standalone VR headsets.
Hotham has a couple of high level goals, or "guiding lights". Sometimes they're in alignment, sometimes they contradict one another, but they generally take the following order of precedence:
- Optimised for maximum possible performance on mobile VR headsets
- Boring, simple to reason about
- Easy to build simple VR applications quickly; possible to build complex things with more effort
- Support the Rust ecosystem and open standards
- Ergonomic API
In addition to these goals, Hotham also has several "non goals" - things that are not considered within the scope of the project:
- Device interoperability
- PCVR support
- Non-VR support
- Non-Rust scripting language
- Large, fully featured editor
- Exotic, unique design
At a high level, Hotham is just glue between OpenXR, Vulkan, the Rapier physics engine and the Oddio audio library, using hecs
, an Entity Component System as its "central store of truth". In another lens, Hotham is essentially a "synchronisation" system between a simulation (eg. you game or application) and "the outside world", eg the headset display, user input, speakers, etc.
With this lens in mind, main loop of a Hotham application is as follows:
- Collect state about input from the external (from the engine's point of view) environment, eg. user input, audio state, physics engine, etc. In Hotham we wrap up these interfaces to the external environment in
Resource
s. A function that updates theWorld
based on aResource
is called asystem
. - With the external state in hand, update the state of the world
World
; that is, a database ofEntity
s representing all the live data inside the engine. - Execute the "game logic", a function that takes the current, updated state of the
World
, runs a simulation and then emits the new, updated state of theWorld
. - Synchronise the updated
World
back to the external environment, eg. rendering engine, audio state, etc. by runningsystems
that read theWorld
and write to someResource
.
It should go without saying that software is complex (game engines, doubly so) and this diagram represents a high level "idealised view" of things. There are instances where a system may read or write to multiple Resource
s, or a system just updates the World
without any reference to an external Resource
.
NOTE We currently call an interface to the external environment
Resource
, but they're all named<X>Context
, eg.XrContext
, which makes no sense. This should be fixed - please feel to suggest better names!
We use ash
because we believe that in order to achieve our goal of high performance and boring system design, it's easiest to use Vulkan directly. Vulkan has first class support on our target device (the Oculus Quest 2), and is a well designed, open standard.
Alternatives to ash
would either be a safe, high level wrapper like vulkano
or an even higher level api like wgpu-rs
. We shy away from high level wrappers as they add extra abstraction between us and the raw hardware. This is doubly so for wgpu
, which while a fantastic project, is less suited for the task of "high performance on this specific hardware".
In addition, the majority of Vulkan documentation/resources assume use of the C Vulkan API. As ash
is almost a direct 1:1 mapping with the C API, it makes it much easier to take advantage of existing knowledge than having to translate this into a higher level Rust API.
The Rust ecosystem has seen a flurry of development around Entity Component Systems, APIs that help applications manage their representation of the simulation. Some great background on why the ECS architecture is so popular in Rust is outlined in the article Specs and Legion, two very different approaches to ECS by the wonderful Coraline Sherratt.
So why hecs
? Well, first, it's very fast. We like that. Second, it has a small API footprint, and follows the principle of "do one thing and do it well". We also like that. It's also well maintained by the fabulous @Ralith, which we very much like.
The obvious contenders for hecs
would be bevy-ecs
or legion
. Interestingly, Hotham originally used legion
as it does have an (initially) simpler, more user friendly API that's easier to hit the ground running with. As a person who was not as familiar with the ECS architecture, hecs
seemed a bit more intimidating and harder to get starter with.
However, legion
appears to be abandoned, and its API actually introduces quite a bit of friction when working with more complex systems.
bevy-ecs
is (rightly so) very tightly coupled with the bevy
ecosystem, and so isn't suitable for our purposes. However it is inspired by work done on hecs
, which is a strong endorsement.
At first glance our use of openxr
may be bizarre. On the one hand, we've said "we want to be laser focussed on the hardware we support", and then on the other, we use this very high level, abstract API for interfacing with the VR headset.
The answer comes down to openxr
is just a better API. It's a very well designed open standard. The specification is well defined and Facebook, the manufacturers of our target hardware, the Oculus Quest 2, are "all in on OpenXR". This makes use of openxr
the obvious choice for the platform.
Finally, openxrs
, the Rust wrapper around openxr
is another excellent @Ralith creation.
Given we're interacting with very specific hardware, the only alternative would be Facebook's deprecated, proprietary libovr
API. But this is a non-starter for the above reasons.
Spatialised audio is a hard requirement for any 3D game, but it's especially important for VR to provide a feeling of immersion. There aren't many good spatial audio libraries in Rust, except for oddio
, which does pretty much exactly what we want. It is, again, another great @Ralith library.
There aren't any other alternatives for spatialised audio in Rust that I'm aware of. However, another alternative might be to wrap Oculus' proprietary Oculus Audio libraries. This is a case of conflicting goals: on the one hand we want to support the Rust ecosystem, on the other hand the Oculus Audio library is extremely advanced, and would bring a lot of benefits to users. This is a decision that will need to be revisited in future.
There are a number of maths libraries available in Rust, but we've chosen nalgebra
. The reasons being that it's got fairly good performance characteristics, a straightforward API, and most importantly is the maths library used by rapier
.
Hotham initially used cgmath
but rapier
's hard dependency on nalgebra
made it a no-brainer switch.
glam
is also a well supported and widely used library, however given rapier
's hard dependency on nalgebra
it would be very difficult to justify having two completely separate maths libraries.
Physics is essentially mandatory in any 3D game; realistic, responsive physics is absolutely essential to create an immersive, believable VR experience. rapier
is the most fully fledged physics engine written in pure Rust, and is the physics engine of choice for the bevy
project. It has good performance characteristics and is well supported.
The only other viable alternative would be a Rust wrapper around NVidia's physx
, but this would clash with our goal of supporting the Rust ecosystem.
glTF
is a well defined, open standard for asset interchange. This means we get to leverage widespread existing support for exporters/importers and other kinds of tooling.
We use the gltf-rs crate to do all the messy gltf
parsing for us.
Asset interchange types are never exactly what you want. Many engines have their own proprietary formats that makes it easier for them to support many interchange formats eg. FBX, OBJ, etc. However, there's a good case to be made that this really is a legacy proposition: with the advent of a robust, open standard interchange format, there's very little to gain from creating Yet Another Asset Format. However there are very real concerns about how glTF
scales, particularly with large scenes or scenes with many interdependent assets. Time will tell.