A backend-neutral render hardware interface for C++20 game engines. Vulkan today; the dispatch seams for Metal and D3D12 are kept in place.
It covers the core device/resource layer, acceleration structures and SBT, a
bindless descriptor set, the program/pipeline cache, a GPU profiler, a
dual-queue resource uploader, typed pipeline descriptions with a hand-declared
descriptor Layout model, OffsetAlloc, TimelineDeferral, an ImGui layer and
a glTF loader.
- Handles are values, not pointers. Every resource is a POD struct wrapping a
backend handle plus a
cookie— a stable identity used as the descriptor-set cache key, because a raw backend handle is unsafe as identity (handles get reused).cookie == 0means empty. - One barrier vocabulary.
ResourceStatecombines access and layout into a single bit set; the backend derives image layouts, access masks and stage masks from it. Stage narrowing is optional and conservative by default. - Dynamic rendering only. There are no render-pass or framebuffer objects.
- No reflection. Descriptor layouts are declared explicitly by the caller
(
rhi::Layout), not derived from SPIR-V. That keeps the program layer free of any shader-bytecode format. - Compile-time backend selection. Each handle carries a
union { vk; mtl; }guarded byRHI_DEVICE_IMPL_*, and each entry point guards onIsTargetSelected(). In a single-backend build that folds to a constant.
cmake -B build -G Ninja -DRHI_BUILD_TESTS=ON
cmake --build build
ctest --test-dir buildRequires CMake ≥ 3.24 and a C++20 compiler. Vulkan headers, volk and VMA are fetched automatically if not already present.
| Option | Default | |
|---|---|---|
RHI_BACKEND_VULKAN |
ON |
|
RHI_BACKEND_METAL |
OFF |
seams only — the implementation is not ported |
RHI_ENABLE_RAYTRACING |
ON |
acceleration structures, RT pipelines, SBT |
RHI_FRAMES_IN_FLIGHT |
2 |
sizes the command ring, scratch allocators, deferral |
RHI_BUILD_SHARED |
OFF |
|
RHI_BUILD_EXAMPLES |
OFF |
needs SDL3 |
RHI_BUILD_TESTS |
OFF |
|
RHI_ENABLE_IMGUI |
OFF |
|
RHI_ENABLE_GLTF |
OFF |
|
RHI_PROVIDE_VMA_IMPL |
ON |
turn off if the host already compiles VMA |
Engines that already vendor volk, VMA or Vulkan-Headers must not end up with a
second copy. cmake/Dependencies.cmake resolves each dependency in this order:
an existing target in the tree, then RHI_USE_EXTERNAL_<DEP>=ON (require, never
fetch), then an installed config package, then FetchContent from a pinned tag.
# volk/VMA already declared by the engine
add_subdirectory(extern/rhi)
target_link_libraries(my_renderer PRIVATE rhi::core)If the engine already compiles VMA in its own TU, add
-DRHI_PROVIDE_VMA_IMPL=OFF.
Install and find_package(rhi) are also supported.
Code being migrated from the older RI* naming can include
<rhi/compat/RICompat.h>, which aliases the previous type and macro spellings
onto the rhi:: namespace.
cmake/Slang.cmake is an optional build rule — nothing in rhi::core needs
it, and including it is what turns it on:
include(Slang)
rhi_add_slang_shaders(my_renderer
SOURCES ${CMAKE_CURRENT_LIST_DIR}/shaders/scene.vert.slang
${CMAKE_CURRENT_LIST_DIR}/shaders/scene.frag.slang
${CMAKE_CURRENT_LIST_DIR}/shaders/common.slang
OUTPUT_DIR ${CMAKE_BINARY_DIR}/compiled_shaders) # optionalEach source whose name ends in a stage suffix — .vert .frag .comp .cs
.geom .tesc .tese .rgen .rchit .rmiss .rahit .rint .rcall
.rt .3d, before the .slang — is compiled to SPIR-V as
<OUTPUT_DIR>/<name>.spv (scene.vert.slang → scene.vert.spv). Everything
else in SOURCES is an include-only module: it gets no compile rule, but it
does become a dependency of every shader that could import it, so editing a
shared module rebuilds them. The outputs are attached to <target> through a
<target>_shaders custom target.
Shaders are compiled with -fvk-use-entrypoint-name, which keeps the Slang
function name as the SPIR-V OpEntryPoint — that name is how the program layer
finds an entry point, so it is not optional.
slangc is resolved in this order: -DRHI_SLANGC=<path>, then a slangc on
PATH, then the pinned prebuilt release downloaded into the build tree at
configure time. If none of those produce a compiler, configuration still
succeeds with a warning and no shaders are built.
| Option | Default | |
|---|---|---|
SLANG_VERSION |
2026.11 |
pinned release for the download |
RHI_SLANG_DOWNLOAD |
ON |
set OFF for an offline build |
RHI_SLANGC |
(empty) | path to a slangc to use as-is |
RHI_SLANG_FLAGS |
(see the module) | flags passed to every shader |
cmake -B build-examples -G Ninja -DRHI_BUILD_EXAMPLES=ON
cmake --build build-examples
./build-examples/examples/00_clearexamples/common/ |
the SDL3 harness — window, WindowHandle, swapchain create/recreate, command ring, frame loop. This is the file to copy into an engine. |
examples/00_clear |
acquire, clear to an animated colour, present. No Program, no pipeline, no shader. |
examples/01_shader |
a fullscreen triangle from a Slang shader through Program + GraphicsPipelineDesc, with a push constant. |
examples/02_mesh |
vertex/index buffers staged into device-local memory, a VertexInputDesc, a depth buffer and an MVP push constant. |
Every example accepts --debug / --no-debug (validation layers; on by default
in a debug build), --no-vsync, --video-driver <name> (x11 for RenderDoc,
which cannot capture a Wayland surface) and --frames <n> (quit after n
frames, for unattended smoke tests). Escape closes the window.
SDL3 is resolved the same way volk and VMA are — an existing SDL3::SDL3
target, then an installed package, then FetchContent of RHI_SDL3_TAG — so an
engine that already vendors SDL does not get a second copy. The harness fills
rhi::WindowHandle from SDL's native window properties rather than calling
SDL_Vulkan_CreateSurface, because that handle is the seam rhi actually offers.
01 and 02 need slangc to compile their .slang sources; if none is found and
the pinned prebuilt cannot be downloaded, configuration reports it and builds
only 00_clear.
GPL-2.0-or-later. Contact the author for commercial licensing.