Down The Rabbit Hole w/ Oculus Quest: Developer Best Practices + The Store

Oculus Developer Blog
Posted by Chris Pruett
May 3, 2019

My name is Chris Pruett, and I'm the Director of Content Ecosystem at Oculus. I gave a talk at the 2019 Game Developers Conference in San Francisco back in March titled Down the Rabbit Hole with Oculus Quest, and wanted to share these insights with the larger community. In yesterday’s post I provided an overview of Quest’s hardware/software stack, and today I will outline a few of the best practices for developing games for Quest, along with a few insights into our Quest store policy.

Development Best Practices

It’s important to understand how Quest’s graphics architecture differs from a PC video card. Most performance optimization on Quest is about formatting art assets correctly rather than writing faster code, and to do that, it’s critical to understand how to avoid bottlenecks in the graphics pipe.

On the CPU side, draw calls are a common source of overhead. A draw call is a command to draw a mesh that comes with a package of state: a mesh, a shader, usually some textures, and whatever other data is required to draw. The graphics driver on the CPU has to do work to prepare each call for the GPU (for example, shader uniforms must be bound to actual vertex streams), and this preparation takes time. One of the most fundamental optimizations is to reduce the total number of draw calls required to draw the scene.

But it’s worth digging deeper on this topic. The key thing about draw calls is that they can vary dramatically in terms of cost. Some draw calls are very cheap while others are very expensive. The actual source of overhead is not so much the draw call itself, but change of state between draw calls. If you draw the same object with the same texture and the same shader over and over again, you’ll find that the first call is pretty expensive and all subsequent calls are very cheap, and this is because the state required to draw the mesh (the shader and texture) were cached by the driver and reused. Only the first call invokes heavy data prep--the subsequent calls just reuse the prepared data. Therefore, rather than just reducing the total number of draw calls in the scene, a more accurate way to think about this sort of optimization is to consider the total number of state changes required per frame.

For example, if you have 10 objects with 10 different textures in your scene, each one of those objects will be a unique draw call with a unique set of data. A different mesh, different textures, different shader parameters, maybe even a different shader. But if you combined all of those objects into a single texture (an “atlas texture”) and adjusted the UV mapping of your objects to point into that single texture, your draw call overhead would decrease because the texture only needs to be bound once to draw all 10 objects. If you could further use the same shader and shader parameters for those objects, you would significantly cut the cost of the scene. If you’re a Unity developer, you would achieve this by using the same Material, with the same shader and same texture on all of your objects. If you can mark those objects as “batching static,” you can even use the same mesh for all of your objects. Here’s a graph that outlines the relative costs of different types of draw call state changes (taller bars indicate more time spent to execute the draw call):

There are a few common patterns we see from devs who effectively reduce their draw call and state change overhead, such as the use of baked lighting and smart static batching of environment meshes. Here’s an example of a scene from Dead and Buried that was optimized at the art asset level to run well on Quest:

On the GPU side, there are some advantages to the Adreno 540’s binning architecture. For example, 4X MSAA costs between 1.25 ms and 1.54 ms per frame, depending on the complexity of your fragment shaders. To maximize performance on this GPU, it’s important to understand that certain full screen effects (e.g. bloom, blurs, etc) require the entire scene to be resolved before they can be processed and are therefore expensive on this hardware. The same is true for other sorts of dependent renders, such as real-time mirrors and reflections. Finally, discarding a fragment in the middle of a fragment shader (via discard, alpha test or clip()) causes the GPU to re-render the entire tile, and should be avoided in all shaders.

Due to the very high resolution that Quest games render to, it’s also possible to become fragment-bound even in scenes that do not have complex fragment shaders. The most common culprit is large transparent objects that overlap each other and end up touching the same pixels many times in a single frame. For example, a particle effect that draws large transparent quads across the entire eye buffer may be more expensive than you expect if the quads have significant overlap and cause lots of pixels to be blended multiple times. Fixed Foveated Rendering can also help cut the overall fragment cost of your frame.

It’s good to keep in mind that increases in eye texture resolution are expensive, but can yield startlingly good results. If you find that you are not GPU-bound, try turning up the resolution on your eye textures.

Games on Oculus Quest can be large, but the data that comprises them needs to be organized a particular way. We recommend a small (< 1 GB, although ideally < 100 mb) executable apk file, along with one or more expansion files. Expansion files can be up to 4 GB in size and can be any format. In Unity, you can put all of your scenes into a Chunk-Compressed Asset Bundle to easily generate an expansion file, which will also improve load times. Quest apps are required to be 64-bit per an update in December of 2019, this helps reduce memory fragmentation, increase your app's performance, reduce load times, and enable loading of larger expansion files.

Though Oculus Quest hasn’t even launched yet, we’re already starting to see new game design patterns emerge from the standalone tetherless form factor. For example, head-based steering seems improved on Quest because the player can turn 360 degrees without getting wrapped up in a cable. Though we still recommend supporting other affordances like snap turns (consider the player who is sitting in a wheelchair and can’t turn around so easily), it feels surprisingly good to drive around an environment with head steering alone.

Another interesting example is recentering. On Rift it’s easy to consider the direction that faces the trackers as “forward” in real world space, but on Quest, there is no obvious “forward.” When the player needs to realign the virtual world to the real world, there is no canonical forward direction around which to rotate the two. The SUPERHOT team solved this problem in an interesting way with the SUPERHOT demo that we showed running on Quest at Oculus Connect 5. In that demo, a spinning pyramid must be smashed at the end of each level in order to progress. If the player is far away from the pyramid, they have to walk over to it in order to get close enough to smash it. The SUPERHOT team added this pyramid and made sure that it was always spawned in the center of the player’s real-world playspace in order to force the player to return to the center of their space before loading the next level. This way, the team can make sure that the player isn’t starting in an awkward location, such as next to a wall, when the next level loads.

The Store + Ecosystem

You might have read about the Concept Approval Process that we announced earlier this year for Oculus Quest. In short, we’re asking that developers submit a concept document to us as early in their development process as they can. Our goal is to support and amplify the titles that look like great fits for our Quest audience, and also make sure that folks who aren’t a fit for the platform can check in before they make a large development investment. The titles that are accepted into the Quest developer program get access to developer support and resources. This process doesn’t apply to Oculus Go or Oculus Rift, just Oculus Quest.

One of the critical nuances to this process that I want to call out is that we are looking for high quality titles that are polished and fun, even if they target a niche audience. Niche, weird, and experimental games are ok! In fact, they are some of our favorites. But we will expect them, just like everything else on the platform, to meet our customer’s expectations in terms of quality and craftsmanship. We’re not looking for tech demos or weekend experiments.

Oculus Quest is launching May 21st, 2019 for $399. We’ll have about 50 titles available at launch, with a lot more coming thereafter. You can keep up with some of the titles we’ve announced with our #QuestCountdown blog posts.

- Chris Pruett