Tephra

**_A modern C++17 graphics and compute library filling the gap between Vulkan and high-level APIs such as OpenGL._**

License: MIT

Current version: v0.5.0

Links: User guide | API Documentation | Discussions

Build status:

Image

About

Tephra aspires to provide a modern alternative to high-level graphics APIs like OpenGL and DirectX 11, while leveraging the benefits of the underlying Vulkan ecosystem. Its goal is to strike a good balance between ease-of-use, performance and relevance. To that end, Tephra provides:

  • A high-level job system for submitting batches of work to the GPU (or to other accelerators that expose the Vulkan API)
  • Low-level command lists that can be recorded in parallel and with minimal overhead
  • An easy and efficient way to create temporary images, staging buffers and scratch memory
  • A simple, general-purpose interface that tries not to force architectural decisions upon the user (such as the concept of frames, requiring recording callbacks or a bindless resource model)
  • The ability to use bleeding-edge features of graphics hardware through direct interoperability with the Vulkan API
  • An introductory user guide, extensive documentation and examples for getting started with using the library without prior knowledge of Vulkan
  • Debugging features, usage validation and testing suite (WIP)

Tephra is being used and partially developed by Bohemia Interactive Simulations.

Comparison to OpenGL / DirectX 11

One of the main differences when moving over from these older graphics APIs is the execution model. Much like in Vulkan, your draw calls don't take effect immediately in Tephra, but are instead recorded into either jobs or command lists that then get executed at a later time. There is no "immediate context". This allows for easy parallel recording and full control over the execution of workloads. Recording commands is usually done in two passes. A "job" first defines high-level commands such as:

  • Allocation of temporary job-local resources
  • Clears, copies, blits and resolves
  • Render and compute passes specifying target resources and forming a set of command lists to execute
  • Resource export commands and Vulkan interop commands

The actual draw and dispatch commands then get recorded into the command lists of each pass after the job itself has been finalized. For convenience, a callback function can be optionally used to record a small command list in-place to help with code organization.

While Tephra handles most of the Vulkan-mandated synchronization automatically from the list of job commands, analyzing commands recorded into command lists would have unacceptable performance overhead. This could ordinarily be circumvented by manually specifying all the resource accesses of each render / compute pass, but for the majority of read-only accesses, the library offers the much more convenient "export" mechanism. Once an image or buffer is written to by a prior command or pass, it can be exported for all future accesses of a certain type, for example as a sampled texture. In effect, for a resource used inside shaders, this means that you generally only need to specify how it is going to be read in the future after each time you write into it. In most cases that only needs to be done once.

Another system inherited from Vulkan is its binding model. By default, resources get bound as descriptors in sets, rather than individually. You can think of a material's textures - the albedo map, normal map, roughness map, etc - as one descriptor set, through which all of its textures get bound to a compatible shader pipeline at the same time. Alternatively, you can use the "bindless" style of managing a global array of all of your textures inside a single giant descriptor set that you then index into inside your shaders. Tephra streamlines working with either method.

Comparison to Vulkan and other Vulkan abstractions

Starting from the initialization stage, Tephra already provides amenities for interacting with the varied world of Vulkan devices. Arbitrary number of queues can be used from each supported queue family, irrespective of the actual number exposed by Vulkan. Feature maps and format utilities further help handle hardware differences. Vulkan profile support is planned, making the process of choosing and relying upon a specific set of hardware features even easier. Overall, the initialization process is greatly simplified compared to raw Vulkan, akin to using the vk-bootstrap library.

Tephra leverages VMA for all of its resource allocations. On top of that, it allows efficient use of temporary resources within each job. Requested job-local resources can be aliased to the same memory location to reduce memory usage if their usage does not overlap. Growable ring buffers provide temporary staging buffers for easy uploading of data. The pools that all these reusable resources are allocated from are controllable and configurable. In general, the library tries to avoid allocations whenever possible, opting instead for pooling and reuse.

RAII is used to manage the lifetime of resources and other objects. Their destruction is delayed until the device is done using the objects, so they can be safely dropped even right after enqueuing a job. The idea of buffer and image views has been expanded upon and nearly all interactions with resources are done through these non-owning views. They can reference the entire resource, or just its part, and are relatively cheap to create on the fly.

Automatic synchronization is implemented between all job commands submitted to the same queue. The implementation tries to minimize the number of barriers without reordering the commands - the control of that is left in the hands of the user. All dependencies are resolved on a subresource level, including byte ranges for buffers and array layers / mip levels for images. Synchronization across different queues is handled with timeline semaphores and resource exports in a thread safe manner.

Many other Vulkan abstractions opt for render graphs to manage synchronization and resource aliasing. Tephra's approach works the same as a render graph that does not reorder passes, but has a smaller API footprint, does not force resource virtualization and is already familiar to users of last-gen graphics APIs. A render graph solution can be easily implemented on top of Tephra, if desired.

Descriptor sets differ from Vulkan's by being immutable. Changing them requires waiting until the device is done with any workload that uses it, which is infeasible in practice. Instead, Tephra recycles and reuses old descriptor sets to create new ones in the background. Besides these ordinary descriptor sets, a mutable descriptor set implementation is also provided. It can be useful for emulating the binding of individual resources, or to assist with a bindless resource model.

Tephra provides many other abstractions around Vulkan, such as pools, pipelines, swapchain and others to form an all-encompassing, high-level-ish graphics library. You generally do not need to use the Vulkan API directly, except when working with extensions that Tephra does not natively support, or when interacting with various device properties and features.

Feature list

The following features are already present:

  • All of the compute and graphics commands supported by core Vulkan
  • Automatic synchronization and resource state tracking inside and across queues
  • Temporary resource allocator that leverages aliasing to reduce memory usage
  • Support for multi-threaded recording and device-level thread safety
  • Improved image and buffer views
  • Safe delayed destruction of Vulkan handles
  • Timestamp, occlusion and pipeline queries
  • Interoperability with plain Vulkan (WIP)
  • Native support of commonly used Vulkan extensions (WIP)
  • Debug logging, statistics, tests and partial usage validation (WIP)

The following features are planned and will likely be available in the future:

  • Ray tracing features (see raytracing branch)
  • Better handling of dynamic pipeline state
  • Improved pipeline building and management
  • Vulkan profiles

The following features are out of scope for the library and won't be included:

  • Platform-dependent window management - use GLFW or a similar library instead
  • Shader compilation and reflection - use the existing Vulkan ecosystem
  • Sparse buffers and images
  • Linear images
  • Rendering algorithms - this is not a renderer or a game engine

See the user guide for more detailed explanations and inline code examples of Tephra's features, or the examples folder for a runnable showcase.

Prerequisities

  • Tephra is a C++ library. It makes use of C++17 features, the standard library and C++ exceptions
  • Visual Studio 2022 (see build/Tephra.sln) or CMake 3.15
  • Vulkan headers version 1.3.239 or newer, provided with the SDK here
  • Compatible devices must support Vulkan 1.3 or newer
  • While any x64 platform is supported, Tephra is being used and tested mainly on Windows

Building the documentation:

  • Python 3.6 or newer
  • Doxygen 1.8.15 or newer
  • The Jinja2 and Pygments Python packages

Contributing

Feel free to create issues, submit pull requests for non-trivial changes and participate in Discussions. Submitting examples, validation and tests is also very appreciated.