Overview
fastgltf is a speed and usability focused glTF 2.0 library written in modern C++17 with minimal dependencies. It uses SIMD in various areas to decrease the time the application spends parsing and loading glTF data. By taking advantage of modern C++17 (and optionally C++20) it also provides easy and safe access to the properties and data. It is also available as a C++20 named module.
The library supports the entirety of glTF 2.0 specification, including many extensions. By default, fastgltf will only do the absolute minimum to work with a glTF model. However, it brings many additional features to ease working with the data, including accessor tools, the ability to directly write to mapped GPU buffers, and decomposing transform matrices.
Why use fastgltf?
There are many other options for working with glTF in C and C++, including the two most popular libraries tinygltf and cgltf. These have been around for years and support virtually everything you need, so why would you even switch?
This table includes a quick overview of a comparison of the general quality-of-life features of the popular glTF libraries.
cgltf |
tinygltf |
fastgltf |
|
|---|---|---|---|
glTF 2.0 reading |
✔️ |
✔️ |
✔️ |
glTF 2.0 writing |
✔️ |
✔️ |
✔️ |
Extension support |
✔️ |
🟡¹ |
✔️ |
Image decoding (PNG, JPEG, …) |
✔️ |
✔️ |
❌ |
Built-in Draco decompression |
❌ |
✔️ |
❌ |
Memory callbacks |
✔️ |
❌ |
🟡² |
Android asset functionality |
❌ |
✔️ |
✔️ |
Accessor utilities |
✔️ |
❌ |
✔️ |
Sparse accessor utilities |
🟡³ |
❌ |
✔️ |
Matrix accessor utilities |
🟡³ |
❌ |
✔️ |
Node transform utilities |
✔️ |
❌ |
✔️ |
¹ tinygltf does provide the JSON structure for extension data, but leaves the deserialization for you to do. ² fastgltf allows the user to allocate memory for buffers and images. It does not provide any mechanism for controlling all the heap allocations the library performs. ³ cgltf supports sparse accessors and matrix data only with some accessor functions, but not all.
You can read more about the accessor utilities from fastgltf here.
fastgltf follows C++’s concept of “you don’t pay for what you don’t use” by only doing the absolute minimum by default. Without specifying any options, fastgltf will only parse the glTF JSON. For buffers and images, fastgltf will by default only either give you a std::vector, when the data is embedded within the glTF, or just plain old URIs. While fastgltf only does the minimum by default, it provides a lot of extra features that can be bundled together.
By using modern C++ features, the code that reads data and properties from the glTF becomes simpler and vastly more descriptive, guaranteeing code-correctness.
A big factor for this improvement is the use of types which enforce certain properties about the data, like e.g. std::variant or std::optional.
Compared with tinygltf, where, for example, optional values are simply represented by a boolean or a -1 for indices, this is a big improvement.
The biggest difference, which may not be as relevant to everyone, is the drastic increase in deserialization speed. In some cases, fastgltf is at least 2 times quicker than its competitors, while in others it can be as much as 20 times. You can read more about fastgltf’s performance in the performance chapter.
Usage
fastgltf is a pure C++17 library and only depends on simdjson. By using the included CMake 3.11 script, simdjson is automatically downloaded while configuring by default. The library is tested on GCC 9, GCC 10, Clang 13, and MSVC 14 (Visual Studio 2022) using CI. fastgltf is also available from vcpkg and conan.
The following snippet illustrates how to use fastgltf to load a glTF file.
#include <fastgltf/core.hpp>
#include <fastgltf/types.hpp>
bool load(std::filesystem::path path) {
// Creates a Parser instance. Optimally, you should reuse this across loads, but don't use it
// across threads. To enable extensions, you have to pass them into the parser's constructor.
fastgltf::Parser parser;
// The GltfDataBuffer class contains static factories which create a buffer for holding the
// glTF data. These return Expected<GltfDataBuffer>, which can be checked if an error occurs.
// The parser accepts any subtype of GltfDataGetter, which defines an interface for reading
// chunks of the glTF file for the Parser to handle. fastgltf provides a few predefined classes
// which inherit from GltfDataGetter, so choose whichever fits your usecase the best.
auto data = fastgltf::GltfDataBuffer::FromPath(path);
if (data.error() != fastgltf::Error::None) {
// The file couldn't be loaded, or the buffer could not be allocated.
return false;
}
// This loads the glTF file into the gltf object and parses the JSON.
// It automatically detects whether this is a JSON-based or binary glTF.
// If you know the type, you can also use loadGltfJson or loadGltfBinary.
auto asset = parser.loadGltf(data.get(), path.parent_path(), fastgltf::Options::None);
if (auto error = asset.error(); error != fastgltf::Error::None) {
// Some error occurred while reading the buffer, parsing the JSON, or validating the data.
return false;
}
// The glTF 2.0 asset is now ready to be used. Simply call asset.get(), asset.get_if() or
// asset-> to get a direct reference to the Asset class. You can then access the glTF data
// structures, like, for example, with buffers:
for (auto& buffer : asset->buffers) {
// Process the buffers.
}
// Optionally, you can now also call the fastgltf::validate method. This will more strictly
// enforce the glTF spec and is not needed most of the time, though I would certainly
// recommend it in a development environment or when debugging to avoid mishaps.
// fastgltf::validate(asset.get());
return true;
}
All the nodes, meshes, buffers, textures, … can now be accessed through the fastgltf::Asset type.
References in between objects are done with a single std::size_t, which is used to index into the
various vectors in the asset.
Examples and real-world usage
The examples/ directory contains some small demos showing how to integrate fastgltf into a 3D renderer. Below is a curated list of notable projects that make use of fastgltf:
Fwog: The examples of this modern OpenGL 4.6 abstraction make use of fastgltf.
wad2gltf: A WAD to glTF converter showcasing fastgltf’s exporting functionalities
Castor3D: A multi-OS 3D engine
Raz: A modern & multiplatform 3D game engine in C++17
vkguide: A modern Vulkan tutorial
lvgl: Embedded graphics library for any MCU, MPU and display type
OptiX_Apps: Official NVIDIA samples for the NVIDIA OptiX Ray Tracing SDK
vk-gltf-viewer: A high performance and highly featured glTF renderer made with Vulkan
Timberdoodle: A research-focused 3D rendering engine made with Vulkan
If you have a project that uses fastgltf and think it would be a great reference or inspiration for others, feel free to send me a private message or open a pull request to add it to the list. The list is meant to highlight notable or widely useful projects, so please consider doing this if your project might serve as a solid example or resource for the community.
Accessor tools
fastgltf provides a utility header for working with accessors. The header contains various functions and utilities for reading, copying, and converting accessor data. All of these tools also directly support sparse accessors to help add support for these without having to understand how they work. These utilities are meant to drastically simplify using glTF accessors and buffers.
You can learn more about this feature of fastgltf in the dedicated chapter: Accessor tools. However, to give a quick overview this is a simple example of how to load the indices of a primitive:
fastgltf::Primitive& primitive = ...;
std::vector<std::uint32_t> indices;
if (primitive.indicesAccessor.has_value()) {
auto& accessor = asset->accessors[primitive.indicesAccessor.value()];
indices.resize(accessor.count);
fastgltf::iterateAccessorWithIndex<std::uint32_t>(
asset.get(), accessor, [&](std::uint32_t index, std::size_t idx) {
indices[idx] = index;
});
}
Performance
In this chapter, I’ll show some graphs on how fastgltf compares to the two most used glTF libraries, cgltf and tinygltf. I’ve disabled loading of images and buffers to only compare the JSON parsing and deserialization of the glTF data. The values and the graphs themselves can be found in this spreadsheet. The following numbers were benchmarked using Catch2’s benchmark tool on an Apple M3 Pro and a Ryzen 5800X using Clang, as Clang showed a significant performance improvement over MSVC in every test.
First, I compared the performance with embedded buffers that are encoded with base64. This uses the 2CylinderEngine asset which contains a 1.7MB embedded buffer. fastgltf includes an optimised base64 decoding algorithm that can take advantage of AVX2, SSE4, and ARM Neon. With this asset, fastgltf is 24.56 times faster than tinygltf using RapidJSON and 7.4 times faster than cgltf.
Amazon’s Bistro (converted to glTF 2.0 using Blender) is another excellent test subject, as it’s a 148k line long JSON. This shows the raw deserialization speed of all the parsers. In this case fastgltf is 1.4 times faster than tinygltf and 5 times faster than cgltf.