Code Sample: Rendering Objects in Parallel Using Vulkan* APIs

File(s):Download
License:Intel Sample Source Code License Agreement
Optimized for... 
OS:64-bit Windows* 7, 8.1 or Windows® 10
Hardware:GPU required
Software:
(Programming Language, tool, IDE, Framework)
Microsoft Visual Studio* 2017, Qt Creator 4.5.0, C++ 17, Qt 5.10, Vulkan* 1.065.1 SDK, ASSIMP 4.1.0 library
Prerequisites:Familiarity with Visual Studio, Vulkan* API, 3D graphics, parallel processing.


Introduction

One of the industry’s hottest new technologies, Vulkan APIs support multithreaded programming, simplify cross-platform development and have the backing of major chip, GPU and device-makers. The API is a collaborative effort by the industry to meet current demands of computer graphics. It is a new approach that emphasizes hiding the CPU bottleneck through parallelism, and allowing much more flexibility in application structure. Aside from components related only to graphics, the Vulkan API also defines the compute pipeline for numerical computation. In all, Vulkan APIs are positioned to become one of the next dominant graphics rendering platforms.

This code and accompanying article (see References below) discuss the process of rendering multiple FBX (Filmbox) and OBJ (Wavefront) objects using Vulkan APIs. The application employs a non-touch graphical user interface (GUI) that reads and displays multiple 3D object files in a common scene. Files are loaded and rendered using linear or parallel processing, selectable for the purpose of comparing performance. In addition, the application allows objects to be moved, rotated, and zoomed through a simple UI. We recommend that you read the article while looking at the code. Make sure you have the examples downloaded and use your favorite code browsing tool.

The code demonstrates the following concepts:

  1. Loaded models displayed in a list
  2. Selected objects identified on-screen with a bounding box
  3. An object information and statistics display showing the number of vertices
  4. The ability to specify either delta or absolute coordinates and rotations
  5. An option to view objects in wireframe mode
  6. Statistics for comparing single- versus multi-threading when reading object files


Get Started

At a high level, when programming using Vulkan, the goal is to construct a virtual device to which drawing commands will be submitted. The draw commands are submitted to constructs called “queues”. The number of queues available and their capabilities depend upon how they were selected during construction of the virtual device, and the actual capabilities of the hardware. The power of Vulkan lies in the fact that the workload submitted to queues could be assembled and sent in parallel to already executing tasks. Vulkan offers functionality to coherently maintain the resources and perform synchronization.

Tutorial: Rendering Objects in Parallel Using Vulkan* APIs

Reading FBX and OBJ files

The first first step is to set up and create the user interface. As we said, this UI is keyboard- and mouse-driven, but it could be enhanced to support touch.

Once the UI is in place, the work begins with reading either an FBX or OBJ file and loading it into memory. The application supports doing this using a single or multiple threads so you can see the difference in performance. We are going to cheat here and use the Open Asset Import Library (assimp) to read and parse the files. Once loaded, the object will be placed in a data structure (Object3D) that we can hand to Vulkan. This is described in detail in the article.

Displaying and manipulating the 3D objects

The main area of the user interface is a canvas where the loaded objects are displayed. These are place in a default location but can be moved anywhere on the canvas so they do not overlap. When you select an object from the list of loaded items it is highlighted with a bounding box. Once selected, you can move, rotate or resize the object by entering new coordinates or size into the form. Again, you can read the details in the accompanying article.

Using Vulkan to render the 3D objects

Loading the objects from memory and displaying them on the screen is handled gracefully by Vulkan. The source code contains code to show how to load an object file using Vulkan. About a dozen lines in, the loaded file is sent to the renderer with support for a secondary command buffer to allow object-loading in parallel. The system processor, GPU, and other factors of the host system as well as the size of the object file will determine single- and multi-threaded object rendering times. Your results will vary.

Because of the complexities of the Vulkan APIs, the biggest challenge was building Renderer, which implements application-specific rendering logic for Vulkan Window. Especially challenging was the synchronization of worker and UI threads without using mutual exclusive locks on rendering- and resource-releasing phases. On the rendering phase, this is achieved by separating command pools and secondary command buffers for each Object3D instance. On resource releasing phase, it is necessary to make sure the host and GPU rendering phases are finished.

The key functions of interest in Renderer are:

  • void Renderer::startNextFrame()
  • void Renderer::endFrame()
  • void Renderer::drawObject()
  • void Renderer::initPipeline()

This latter method was required in order to handle different types of graphical objects – those loaded from files and those dynamically generated in the form of bounding boxes that surround the selected object. This caused a problem because they use differing shaders, primitive topologies and polygon modes. The goal was to unify code as much as possible for the different objects to avoid replicating similar code. Both types of objects are expressed by single-class Object3D.


Conclusion

Coding flexibility is a hallmark of low-level Vulkan APIs but it is critical to remain focused on what is going on in each Vulkan step. These lower-level programming capabilities also allows for fine-tuning certain aspects of hardware access not available with OpenGL*. If you take it slow, and build your project in small, incremental steps, the payoffs will include far greater rendering performance, much lower runtime footprint, and greater portability to a multitude of devices and platforms.


References

Alexey Korenevsky, Integrated Computing Solutions, Inc., Vulkan Code Sample: Rendering Objects in Parallel, Rendering Objects in Parallel Using Vulkan* APIs, 2018

Open Asset Import Library


Updated Log

Created May 23, 2018

For more complete information about compiler optimizations, see our Optimization Notice.