A loader generator for Vulkan, OpenGL, OpenGL ES, EGL, GLX, and WGL. Reads Khronos XML spec files and generates C dispatch code. The gloam binary is fully self-contained — XML specs and auxiliary headers are embedded at compile time.
Fast initialization. Extension detection uses pre-baked xxHash hashes
with binary search — zero string comparisons at load time. gloam's enabled-list
API for Vulkan loads only the function pointers you actually enabled, skipping
the expensive vkEnumerate*ExtensionProperties calls entirely.
Small output. Generated object files and linked binary sizes are a fraction of the size of comparable loaders.
Correct by default. Bijective alias resolution (--alias) propagates
function pointers between core and extension spellings automatically.
--promoted and --predecessors ensure that both core and extension versions
of functions and enums are available in the generated loader. Merged builds
(--merge) scope extensions per-API to prevent cross-contamination.
Deterministic. Output is byte-identical across runs — safe to check into version control and audit diffs after regenerating.
Vulkan teardown-and-reinitialize cycle on Linux x86_64 (see
vk-api-loader-shootout for full results across platforms):
| Loader | Avg time | Object size | Binary size |
|---|---|---|---|
| GLAD (upstream) | 16437 us | 316,288 bytes | 104,800 bytes |
| Volk | 876 us | 312,912 bytes | 96,568 bytes |
| gloam (discover) | 9896 us | 48,104 bytes | 55,696 bytes |
| gloam (enabled-list) | 149 us | 48,104 bytes | 55,712 bytes |
cargo install gloamOr build from source (requires Rust 1.88+):
cargo build --releaseGenerate loaders for every supported API at once:
gloam --api gl:core,gles2,vulkan,egl,glx,wgl --out-path generated --merge c --alias --loaderThis produces a merged GL/GLES2 loader, plus separate Vulkan, EGL, GLX, and WGL loaders — all with full extension coverage, alias resolution, and a built-in library-opening convenience layer.
Output directory:
generated/
.gloam/
manifest.json # provenance + output bill of materials
include/
gloam/
gl.h # merged GL + GLES2 public header
vk.h # Vulkan public header
egl.h # EGL public header
glx.h # GLX public header
wgl.h # WGL public header
KHR/
khrplatform.h # Khronos platform types
EGL/
eglplatform.h # EGL platform types
vk_platform.h # Vulkan platform types
xxhash.h # xxHash header (used by loaders)
src/
gl.c # merged GL + GLES2 loader implementation
vk.c # Vulkan loader implementation
egl.c # EGL loader implementation
glx.c # GLX loader implementation
wgl.c # WGL loader implementation
Without --merge, each API gets its own stem: gl for desktop GL
only, gles2 for GLES2 only, vulkan for Vulkan, etc.
gloam [OPTIONS] <COMMAND>
Options:
--api <SPEC> API specifiers (required for generation; not used by
`gloam lock`). Comma-separated list of
name[:profile][=major.minor] tokens. Profile is
required for GL (core|compat). Version is optional
(latest if omitted). Examples:
gl:core=3.3
gles2=3.0
gl:core=3.3,gles2=3.0
vk=1.3
egl=1.5
--extensions <FILTER> Restrict extensions. Either a path to a file (one
name per line) or a comma-separated inline list.
Omit to include all extensions supported by the
requested API.
--baseline <SPEC> Baseline API versions (same format as --api).
Extensions fully promoted into these versions or
earlier are excluded — they are guaranteed present
in a context of at least the baseline version.
Example: --baseline gl:core=3.3,gles2=3.0
--promoted Include extensions whose commands were promoted into
the requested core version. For example, with
gl:core=3.3, this adds every extension that was
promoted to core in GL 3.3 or earlier, ensuring
that both the core and extension-era function names
and enums are present in the output. Handles both
same-name promotion (e.g. ARB_copy_buffer) and
renamed promotion (e.g. ARB_multitexture ->
glActiveTexture). Scoped per-API in merged builds.
--predecessors Include predecessor extensions of the already-selected
set. If an extension's commands or enums are aliases
of those in a selected extension, the predecessor is
added too. Follows chains to a fixed point, so
indirect predecessors are included. Runs after
--promoted, so promoted extensions also seed the
predecessor search.
--merge Merge multiple APIs of the same spec into a single
output file. Required when combining gl and gles2.
--out-path <DIR> Output directory [default: .]
--quiet Suppress progress messages.
--fetch Resolve XML specs and headers from upstream (via the
GitHub API) instead of the bundled copies, capturing
full provenance. Set GITHUB_TOKEN to lift the API rate
limit. Any fetch failure is fatal.
--lock <MANIFEST> Pin upstream sources to the provenance recorded in a
previous .gloam/manifest.json (or a `gloam lock`
snapshot), for reproducible output. Requires either
--fetch or a gloam build whose bundled files match the
locked blobs. See "Provenance and reproducible builds".
Commands:
c Generate a C loader.
--alias Enable bijective function-pointer alias resolution at
load time. If the canonical slot is null but an alias
was loaded by the driver (or vice versa), the pointer
is propagated to both slots.
--loader Include a built-in dlopen/LoadLibrary convenience layer.
--external-headers
(Vulkan only) Use system-installed Vulkan headers
instead of embedding type definitions. The generated
header includes <vulkan/vulkan_core.h> and the
platform-specific Vulkan headers conditionally,
following the same pattern as Volk. Auxiliary headers
(vk_platform.h, vk_video/*) are not bundled in the
output directory.
lock Write a provenance-only snapshot manifest (no loader output)
pinning every supported upstream source — at the current bundle,
or at upstream HEAD with --fetch. Reuse it later with --lock.
--out <FILE>
Output path for the snapshot [default: manifest.json].
The extension-related flags are orthogonal and compose freely:
| Flag | What it does |
|---|---|
--alias |
Runtime: propagates loaded function pointers to alias slots at load time |
--promoted |
Selection: adds extensions promoted into the requested core version |
--predecessors |
Selection: adds predecessor extensions of the already-selected set |
--baseline |
Selection: excludes extensions fully promoted into the baseline version |
Every generated loader records exactly which upstream sources produced it. Each
file's repository, git describe version, upstream commit, and git blob hash
appear in three places:
- the comment header of every generated
.h/.cfile; gloam --version, which lists the embedded bundle's provenance; and.gloam/manifest.json, written to the root of every output tree — a machine-readable bill of materials (gloam version, the source provenance pin set, and agit hash-objectblob hash for every emitted file). It contains no timestamps, so the same inputs and gloam version produce byte-identical output.
gloam lock writes a provenance-only snapshot manifest that pins every
supported upstream source:
gloam --fetch lock --out manifest.json # snapshot upstream HEAD (via the GitHub API)
gloam lock --out manifest.json # snapshot the embedded bundleFeed that manifest — or any generated .gloam/manifest.json — back with
--lock to regenerate against exactly those pinned sources:
gloam --api gl:core=4.6 --lock manifest.json --out-path generated c --alias --loaderA locked regeneration with otherwise identical arguments is byte-identical to
the original (the --lock flag itself is excluded from the recorded command
line). If the manifest lacks provenance for a file the new command requires,
gloam refuses rather than silently substituting a different version. --lock
needs either --fetch (to fetch the pinned blobs) or a gloam build whose
bundled files already match the pinned blobs.
This is the mechanism used to generate several loaders from one consistent
upstream snapshot: take a single gloam lock snapshot, then drive each
generation with --lock.
Each API gets a context struct (GloamGLContext, GloamVulkanContext,
etc.) with named members for every loaded function pointer. A global
context variable is declared for each API (gloam_gl_context,
gloam_vk_context, etc.), and dispatch wrappers route calls through it:
// This expands to a dispatch wrapper that calls the function pointer
// in the global context:
glClear(GL_COLOR_BUFFER_BIT);For GL, EGL, GLX, and WGL, dispatch uses #define macros. For Vulkan,
dispatch uses static force-inlined functions to avoid macro namespace
collisions with the upstream Vulkan headers and libraries like the
Vulkan Memory Allocator.
The emitted loader headers have full function prototypes specifically for IntelliSense, so you get clear hints when developing in Visual Studio.
The context struct also has named members for checking feature version support and extension presence at runtime:
if (GLOAM_GL_VERSION_3_3) { /* GL 3.3 core available */ }
if (GLOAM_GL_ES_VERSION_3_0) { /* OpenGL ES 3.0 available */ }
if (GLOAM_GL_ARB_draw_indirect) { /* ARB_draw_indirect available */ }These macros expand to the corresponding named member on the global
context (e.g. gloam_gl_context.ARB_draw_indirect).
gloam generates two Vulkan loading modes. Most Vulkan applications should use the enabled-list API.
The caller creates their Vulkan instance and device normally, then passes the same API version and extension lists to gloam. gloam loads only those function pointers, sets the corresponding presence flags, and runs alias resolution. No enumeration calls, no heap allocation, no ambiguous pointers.
// Phase 0: open the Vulkan library, load global-scope bootstrap functions.
// Pass NULL to let gloam find and open libvulkan; or pass your own handle.
gloamVulkanInitialize(NULL);
// Create your VkInstance as usual...
VkApplicationInfo appInfo = { ... };
VkInstanceCreateInfo instanceCreateInfo = { ... };
vkCreateInstance(&instanceCreateInfo, NULL, &instance);
// Phase 1: load instance-scope functions.
// These are the same values you passed to VkApplicationInfo and
// VkInstanceCreateInfo.
gloamVulkanLoadInstance(instance,
appInfo.apiVersion,
instanceCreateInfo.enabledExtensionCount,
instanceCreateInfo.ppEnabledExtensionNames);
// Create your VkDevice as usual...
VkDeviceCreateInfo deviceCreateInfo = { ... };
vkCreateDevice(physicalDevice, &deviceCreateInfo, NULL, &device);
// Phase 2: load device extension functions.
// These are the same values you passed to VkDeviceCreateInfo.
gloamVulkanLoadDevice(device, physicalDevice,
deviceCreateInfo.enabledExtensionCount,
deviceCreateInfo.ppEnabledExtensionNames);
// ... use Vulkan normally ...
// Cleanup: close library handle and zero context.
gloamVulkanFinalize();Each function also has a *Context variant that takes an explicit
context pointer instead of using the global gloam_vk_context
(e.g. gloamVulkanInitializeContext(ctx, NULL)).
A gloamVulkanInitializeCustom(getInstanceProcAddr) variant is
available when you already have a vkGetInstanceProcAddr and want to
skip the library-opening step.
Some device extensions provide instance-scope query functions that
applications need to call before creating a VkDevice — for example,
VK_KHR_fragment_shading_rate has
vkGetPhysicalDeviceFragmentShadingRatesKHR, which takes a
VkPhysicalDevice and is used to query the extension's supported
shading rates before deciding whether to enable the extension at device
creation. This is a rare special case, but gloam supports it with an
optional Phase 1.5 step:
gloamVulkanLoadInstance(instance, ...);
// Phase 1.5 (optional): pre-load instance-scope functions for device
// extensions you want to query before creating the VkDevice.
gloamVulkanLoadPhysicalDeviceExtension(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
// Now you can call vkGetPhysicalDeviceFragmentShadingRatesKHR to query
// supported shading rates and decide whether to enable the extension.
vkGetPhysicalDeviceFragmentShadingRatesKHR(physicalDevice, ...);
// Create your VkDevice with the extensions you chose...
vkCreateDevice(physicalDevice, &deviceCreateInfo, NULL, &device);
// Phase 2 loads everything for the enabled device extensions, including
// re-loading any instance-scope functions that Phase 1.5 already set.
gloamVulkanLoadDevice(device, physicalDevice, ...);The singular gloamVulkanLoadPhysicalDeviceExtension takes a single
extension name. The plural gloamVulkanLoadPhysicalDeviceExtensions
takes a count and array, for loading multiple device extensions at once.
These functions do not set the GLOAM_VK_* extension presence
macros — those are only set by gloamVulkanLoadDevice after the
extension is actually enabled.
With --loader, gloam also generates a discovery API that calls
vkEnumerate*ExtensionProperties to auto-detect available extensions:
gloamLoaderLoadVulkan(NULL, VK_NULL_HANDLE, VK_NULL_HANDLE);
// ... create instance ...
gloamLoaderLoadVulkan(instance, VK_NULL_HANDLE, VK_NULL_HANDLE);
// ... create device ...
gloamLoaderLoadVulkan(instance, physical_device, device);
// Cleanup.
gloamLoaderUnloadVulkan();Note: Discovery mode detects which extensions the driver supports, but Vulkan's validity rules require that you enable an extension at instance or device creation before using its commands or structures. Extension presence in the context does not mean the extension is enabled. For this reason, the enabled-list API is the recommended approach for Vulkan — it loads exactly what you enabled, with no ambiguity.
Discovery mode is also slower, because the Vulkan enumeration APIs are expensive — the loader library scans ICDs and implicit layers on each call. See vk-api-loader-shootout for detailed benchmarks.
For GL, EGL, GLX, and WGL, discovery is the only loading path, and extension presence means you can use it immediately — no enable step is required.
gloam infers the correct vkGet*ProcAddr entry point from the first
parameter type of each command:
| First parameter | Scope | Loaded via |
|---|---|---|
| (none / non-handle) | Global | vkGetInstanceProcAddr(NULL, name) |
VkInstance / VkPhysicalDevice |
Instance | vkGetInstanceProcAddr(instance, name) |
VkDevice / VkQueue / VkCommandBuffer |
Device | vkGetDeviceProcAddr(device, name) |
Add the generated files to your C or C++ project:
- Add
generated/includeto your include path. - Compile the
generated/src/*.cfiles alongside your project. - Include the appropriate header:
#include <gloam/gl.h> // GL (or merged GL + GLES2 with --merge) #include <gloam/vk.h> // Vulkan (or vulkan.h without --merge) #include <gloam/egl.h> // EGL
With --loader, the generated code handles library opening
(dlopen/LoadLibrary) for you. On POSIX platforms you may need to
link -ldl; on Windows no extra link flags are needed.
target_include_directories(myapp PRIVATE generated/include)
target_sources(myapp PRIVATE
generated/src/gl.c
generated/src/vk.c
)
if(UNIX)
target_link_libraries(myapp PRIVATE dl)
endif()gloam embeds compile-time snapshots of upstream Khronos XML specs and
auxiliary headers, along with bundled/provenance.json recording the exact
upstream commit, git describe, and blob hash of each. The binary needs no
external files at runtime.
To refresh the bundled copies (and their provenance):
./scripts/fetch_bundled.sh # = cargo xtask bundleThis fetches every source at upstream HEAD through gloam's own acquisition
path. Set GITHUB_TOKEN to lift the GitHub API rate limit. gloam --version
prints the embedded provenance.
The --fetch CLI flag bypasses the bundled copies and fetches specs
directly from upstream at generation time. This may provide fresher
specs, but is less tested and may encounter upstream spec regressions.
cargo build --release
cargo testRequires Rust 1.88 or later. The fetch feature (enabled by default)
pulls in reqwest for --fetch mode; disable with
--no-default-features if not needed.
See CONTRIBUTING.md for architecture details, spec gotcha documentation, and development guidelines.
gloam is licensed under either of Apache License, Version 2.0 or MIT license at your option.
Generated output includes xxHash by Yann Collet, licensed under the BSD 2-Clause license. The xxHash header is output unmodified and retains its original license notice.
gloam outputs are derived from the Khronos Group XML API Registry specifications, and are licensed by the Khronos group under the Apache 2.0 license.
gloam also uses the ANGLE-specific extension XML registry files for GLES2 and EGL outputs, which are owned by Google and licensed under the BSD 3-clause license.