The PowerVR architecture is quick to support the latest technologies, and the Vulkan API is a case in point. Vulkan is a modern API that enables developers to gain ‘low-level’ access to underlying graphics hardware, ensuring that the performance of that hardware can be maximised.
Any apps displaying large volumes of data such as navigation apps are a great use case for Vulkan. Navigation apps require lots of background processing, while rendering as much relevant information as possible at as high a frame rate as possible. Here, I am going to detail our recent satnav demo. I’ll explain how it makes good use of the Vulkan graphics API, show how its Vulkan and OpenGL ES implementations compare, and reveal why PowerVR is a great fit for running this kind of application with a modern graphics API like Vulkan.
Vulkan and OpenGL ES
First, a refresher on the relevant differences between Vulkan and OpenGL ES. Unlike OpenGL ES, Vulkan was fundamentally designed with complete and explicit control in mind, which among other things enables tight control over distributing workloads across multiple threads.
GPU resources can be allocated on one thread, while other GPU work goes on simultaneously on another. In addition, instead of GPU commands being called and then immediately executed, as in OpenGL ES; in Vulkan, commands are “recorded” to a command buffer (essentially just a list of instructions). That entire command buffer is then submitted to the GPU for rendering. This structure means that command submission is entirely predictable for the developer. They have control over exactly when their render commands are sent to the GPU, as opposed to OGLES where some GPU commands will almost certainly not execute as soon as they are called, but rather at some other arbitrary point in the render loop – beyond the developer’s control. Crucially, command buffers can then be re-used every frame, assuming their contents haven’t changed. They can be recorded once and resubmitted every frame. This construct doesn’t exist in OpenGL ES.
Additionally, command buffers can be made up of multiple secondary buffers, enabling fine control over which portions of a graphics workload are re-recorded every frame. Static buffers are left alone after being recorded once and dynamic buffers are re-recorded at the required interval. This interval will often be every frame, but it doesn’t have to be.
Now, how does that map to our satnav application? For our Satnav demo, we use map data from OpenStreetMap.org, enabling us to query a database for all map elements within specified tile regions. This is a common and intuitive way of handling the data and is perfect for Vulkan as it means we can record all the rendering commands for each tile into its own command buffer. Since the map data doesn’t ever change, this only needs to be done once.
As the camera pans across the map, we need to load in new tiles on a regular basis, but the app needs to stay responsive – we can’t afford to stall the main thread. Thanks to Vulkan, we can allocate the GPU resources for a new tile and record its command buffer all on a separate worker thread, without disturbing the main thread, which carries on rendering and responding to input. For this app, it is enough to have only one additional worker thread, but if we needed to there’s nothing stopping us spawning multiple additional threads to cope with more tiles at once. This could even be scaled dynamically, based on how much CPU resource is permitted for use, or if device power usage needs to be managed. All of this is non-trivial at best in OpenGL ES, but is a fundamental part of Vulkan’s design.
Just as tiles come into view, they can go out again, meaning command buffers are freed up. Instead of de-allocating these and re-allocating when a new tile comes in, we pre-allocate a set of buffers from a command pool object, and from this set we recycle used buffers, reducing the app’s dynamic memory allocation overhead.
Vulkan enables PowerVR to handle all this with much greater efficiency than previous APIs. As a tile-based deferred renderer (TBDR), our hardware is able to take full advantage of having the information required to render up front. Vulkan uses a “render pass” structure (made up of sub-passes containing command buffers) to define a chunk of rendering work. This more closely resembles the basic unit of work used by tile-based renderers than single draw calls. These structures are also convenient for a tiler as they prevent anything that would definitely cause a mid-frame flush between draw calls, which would otherwise mean rendering tiles multiple times. For a more in-depth look at what this means and how the PowerVR architecture works well with Vulkan, take a look at this article by Tobias Hector.
A word about the PowerVR SDK
This app was initially written with Vulkan in mind using the PowerVR SDK, and this meant generating an OpenGL ES version was very straightforward. The SDK reflects the Vulkan API structure but abstracts away the underlying API, so the same code just works for both Vulkan and OpenGL ES. The only significant changes involved descriptor sets (OpenGL ES has no concept of these) and shifting GPU resource allocation to the main thread. Additionally, using the SDK also means the same code just works without hassle on all platforms (Android, Windows and Linux).
Let’s look at how the Vulkan and OpenGL ES versions of the demo compare. The video and graphs below were all generated from running on an Android-based Nexus Player – a consumer device containing a PowerVR G6430 GPU. As you can see in the side-by-side video, the Vulkan implementation consistently delivers a much higher frame rate, with no noticeable drop in performance when new tiles are loaded in. Conversely, in the OpenGL ES version, the base frame rate is lower because more work has to be done to tell the GPU what to do every frame. There is also a stutter when loading in new content as the main thread has to be interrupted to allocate GPU resources.
Map data © OpenStreetMap contributors. Data available under the Open Database Licence.
Now looking at the performance graphs below, the Vulkan implementation runs at a solid 60 frames per second (FPS), with consistently low CPU usage – averaging just under 30%. As expected in the OpenGL ES version, once all of the content is loaded in, the frame rate hovers between 24 and 27 FPS and has an overall higher CPU usage, including one core being completely maxed out throughout. This is clear evidence that Vulkan performs better than OpenGL ES with this workload.
While OpenGL ES still has many valid use-cases, Vulkan is a great API for tasks such as rendering satellite navigation apps, and PowerVR is capable of maximising its efficiency. Vulkan was designed with modern concepts like multi-threading in mind, which is really important when frequently reading in new chunks of map data on-the-fly, without disturbing the main thread, which is left free to render the content, while maintaining an interactive frame rate. This is just one example of how Vulkan’s up-front and explicit nature coupled with PowerVR’s TBDR architecture enables developers to get the most out of their apps.