A mid-level Vulkan wrapper library in Odin. Handles the first ~6K lines of Vulkan boilerplate so new 3D projects start productive immediately.
VKW gives you PBR rendering, mesh loading, camera systems, text rendering, and a pipeline builder — without hiding the Vulkan API. You still record command buffers and bind descriptor sets, but the tedious setup is done for you.
- One-call init/shutdown — Vulkan instance, device, swapchain, render pass, sync objects, VMA allocator
- PBR rendering — Cook-Torrance BRDF with 5-map materials (albedo, metallic-roughness, normal, occlusion, emissive)
- glTF model loading — geometry + PBR textures from .glb/.gltf files
- HDR skybox — equirectangular environment maps
- Lighting — up to 4 directional lights + 8 point lights with UE4 attenuation
- Camera systems — FPS (WASD + mouse) and orbit (drag to rotate, scroll to zoom)
- GPU text rendering — TTF fonts + SVG icons via odin-slug Bezier rendering
- Pipeline builder — create custom graphics pipelines with a config struct instead of 200 lines of Vulkan boilerplate
- ACES tone-mapping — filmic HDR-to-LDR in the fragment shader
- Automatic swapchain recreation on window resize
import vkw "vkw:."
main :: proc() {
renderer := new(vkw.Renderer)
defer free(renderer)
if !vkw.init(renderer) do return
defer vkw.shutdown(renderer)
meshes, materials, textures, ok := vkw.load_mesh_from_gltf(renderer, "model.glb")
defer vkw.destroy_loaded_model(renderer, &meshes, &materials, &textures)
cam := vkw.create_orbit_camera()
lighting := vkw.DEFAULT_SCENE_LIGHTING
for running {
// ... handle SDL events, update camera ...
cmd := vkw.begin_frame(renderer)
if cmd == nil do continue
vkw.update_camera_ubo_orbit(renderer, &cam)
vkw.update_lights_ubo(renderer, &lighting)
vkw.draw_skybox(renderer, cmd)
vkw.bind_mesh_pipeline(renderer, cmd)
for &mesh in meshes {
vkw.draw_mesh(renderer, cmd, &mesh, linalg.MATRIX4F32_IDENTITY)
}
vkw.end_frame(renderer)
}
}See app/main.odin for the complete working example.
| Dependency | Purpose | How to get it |
|---|---|---|
| Odin compiler | Language | odin-lang.org/docs/install |
| Vulkan SDK (1.4+) | Graphics API + glslc shader compiler |
See platform instructions below |
| SDL3 | Windowing, input, Vulkan surface creation | See platform instructions below |
| g++ (or MSVC/clang) | Compile VMA static lib (one-time) | System package manager |
| git | Clone VMA + Vulkan-Headers during VMA build | System package manager |
| Dependency | Location | Purpose |
|---|---|---|
| odin-vma (Capati/odin-vma) | libs/odin-vma/ |
Vulkan Memory Allocator bindings |
| VMA pre-built static libs | libs/odin-vma/libvma_*.a |
Pre-compiled for Linux x86_64 |
| odin-slug | ../odin-slug (sibling directory) |
GPU Bezier text/SVG rendering |
Install dependencies:
# Arch Linux
sudo pacman -S vulkan-devel vulkan-validation-layers sdl3 glslc
# Ubuntu/Debian
sudo apt install libvulkan-dev vulkan-validationlayers libsdl3-dev glslc
# Fedora
sudo dnf install vulkan-devel vulkan-validation-layers SDL3-devel glslcClone and build:
git clone https://github.com/Naughtyusername/odin_vulkan_wrapper
cd odin_vulkan_wrapper
# Clone odin-slug next to this project (required for text rendering)
git clone https://github.com/nickel-lang/odin-slug.git ../odin-slug
# Build VMA static lib (one-time, ~30 seconds)
./build.sh vma
# Build and run the demo
./build.sh runInstall dependencies:
- Odin — download from odin-lang.org
- Vulkan SDK 1.4+ — download from vulkan.lunarg.com. The installer adds
glslcto PATH and sets the%VULKAN_SDK%environment variable —build.batuses both to find it automatically. - Visual Studio 2019+ or Build Tools for Visual Studio — needed to compile the VMA static library (one-time). Install the "Desktop development with C++" workload.
build.bat vmadetects VS automatically viavswhere.exe; no Developer Command Prompt needed. - SDL3 — download the development libraries (
SDL3-devel-*-VC.zip) from libsdl.org. The demo needsSDL3.dllat runtime. Either:- Set
SDL3_PATHto the extracted directory (e.g.set SDL3_PATH=C:\SDL3-3.2.0-VC) —build.batcopiesSDL3.dlltobuild\automatically, or - Copy
SDL3.dllmanually tobuild\, or - Add the directory containing
SDL3.dllto yourPATH
- Set
Clone and build:
git clone https://github.com/Naughtyusername/odin_vulkan_wrapper
cd odin_vulkan_wrapper
:: Clone odin-slug next to this project (required for text rendering)
git clone https://github.com/nickel-lang/odin-slug.git ..\odin-slug
:: Build VMA static lib (one-time, auto-detects MSVC)
build.bat vma
:: Build and run (set SDL3_PATH first if SDL3.dll isn't in build\ or PATH)
set SDL3_PATH=C:\SDL3-3.2.0-VC
build.bat runInstall dependencies:
# Homebrew
brew install molten-vk sdl3 glslc
# Vulkan SDK (alternative) — provides MoltenVK + validation layers
# Download from https://vulkan.lunarg.com/Clone and build:
git clone https://github.com/Naughtyusername/odin_vulkan_wrapper
cd odin_vulkan_wrapper
git clone https://github.com/nickel-lang/odin-slug.git ../odin-slug
./build.sh vma
./build.sh runLinux / macOS:
./build.sh vma # One-time: compile VMA static lib
./build.sh build # Compile shaders + build demo
./build.sh run # Build + run
./build.sh check # Type-check wrapper library only (no binary)
./build.sh shaders # Compile GLSL -> SPIR-V only
./build.sh clean # Remove build artifacts + compiled shadersWindows (from VS Developer Command Prompt):
build.bat vma :: One-time: compile VMA static lib
build.bat build :: Compile shaders + build demo
build.bat run :: Build + run
build.bat check :: Type-check wrapper library only (no binary)
build.bat shaders :: Compile GLSL -> SPIR-V only
build.bat clean :: Remove build artifacts + compiled shadersVKW is a collection-based library. Point the Odin compiler at it:
odin build my_game/ \
-collection:vkw=path/to/vulkan_wrapper/src \
-collection:vma=path/to/vulkan_wrapper/libs/odin-vma \
-collection:slug=path/to/odin-slugThen import in your code:
import vkw "vkw:."VKW's built-in shaders are embedded at compile time via #load. Your custom shaders need to be compiled to SPIR-V before building:
glslc -fshader-stage=vertex my_shader.vert.glsl -o my_shader.vert.spv
glslc -fshader-stage=fragment my_shader.frag.glsl -o my_shader.frag.spvThen load them into a custom pipeline:
config := vkw.DEFAULT_PIPELINE_CONFIG
config.vert_spv = #load("my_shader.vert.spv")
config.frag_spv = #load("my_shader.frag.spv")
config.descriptor_set_layouts = { renderer.global_set_layout, my_layout }
pipe, ok := vkw.build_pipeline(renderer, config)
defer vkw.destroy_custom_pipeline(renderer, &pipe)| Proc | Description |
|---|---|
init(r, config) |
Initialize Vulkan + window |
shutdown(r) |
Destroy everything |
begin_frame(r) |
Acquire image, begin command buffer |
end_frame(r) |
Submit + present |
notify_resize(r) |
Flag swapchain for recreation |
wait_idle(r) |
Block until GPU finishes |
| Proc | Description |
|---|---|
load_mesh_from_gltf(r, path) |
Load glTF model (meshes + materials + textures) |
destroy_loaded_model(r, ...) |
Clean up everything from load_mesh_from_gltf |
create_texture_from_file(r, path) |
Load image as GPU texture |
create_material(r, ...) |
Create PBR material from textures |
load_environment_map(r, path) |
Load HDR skybox |
| Proc | Description |
|---|---|
bind_mesh_pipeline(r, cmd) |
Bind built-in PBR pipeline |
draw_mesh(r, cmd, mesh, model_matrix) |
Draw a mesh instance |
draw_mesh_instanced(r, cmd, mesh, models) |
Draw many instances in one draw call |
draw_shadow_pass(r, cmd, meshes, models, light_dir, scene_min, scene_max) |
Render depth-only shadow map (call before begin_render_pass) |
draw_skybox(r, cmd) |
Draw HDR background |
build_pipeline(r, config) |
Create custom graphics pipeline |
bind_custom_pipeline(r, cmd, pipe) |
Bind a custom pipeline |
| Proc | Description |
|---|---|
create_camera(pos) |
FPS camera (WASD + mouse) |
create_orbit_camera() |
Orbit camera (drag + scroll) |
update_camera(cam, dt, keys, ...) |
Update FPS camera |
update_orbit_camera(cam, ...) |
Update orbit camera |
update_camera_ubo(r, cam) |
Upload FPS camera matrices to GPU |
update_camera_ubo_orbit(r, cam) |
Upload orbit camera matrices to GPU |
| Proc | Description |
|---|---|
update_lights_ubo(r, lighting) |
Upload scene lighting to GPU |
add_dir_light(lighting, ...) |
Add directional light (max 4) |
add_point_light(lighting, ...) |
Add point light (max 8) |
| Proc | Description |
|---|---|
init_text_renderer(r) |
Initialize slug backend |
load_text_font(r, slot, path) |
Load TTF font |
load_text_font_with_icons(r, slot, path, icons) |
Load TTF + SVG icons |
text_begin(r) |
Start text batch |
text_flush(r, cmd) |
Upload + draw text |
draw_debug_overlay(r, info) |
FPS/camera debug text |
vulkan_wrapper/
├── src/ # The wrapper library (import as vkw)
│ ├── wrapper.odin # Core types (Renderer, Config) + init/shutdown
│ ├── init.odin # Vulkan instance, device, VMA bootstrap
│ ├── swapchain.odin # Swapchain, render pass, framebuffers
│ ├── pipeline.odin # Pipeline creation + pipeline builder API
│ ├── commands.odin # Command buffers, sync, begin/end frame
│ ├── resources.odin # Buffers, textures, materials, descriptors
│ ├── mesh.odin # Mesh types, camera, lighting, glTF loader
│ └── text.odin # Slug text/SVG rendering integration
├── app/ # Demo / usage reference
│ └── main.odin
├── shaders/ # GLSL source (compiled to .spv at build time)
├── assets/ # Models, textures, icons (committed — see Demo Assets)
├── libs/odin-vma/ # VMA bindings + pre-built static lib
├── build.sh # Linux/macOS build script
└── build.bat # Windows build script
Shadow map bounds — draw_shadow_pass takes scene_min / scene_max parameters that define the orthographic frustum for the shadow map. These default to {-10, -10, -10} / {10, 10, 10}. If your scene is larger than this and shadows disappear or clip, pass your actual scene AABB:
vkw.draw_shadow_pass(renderer, cmd, meshes, models, light_dir,
scene_min = {-50, -5, -50},
scene_max = { 50, 20, 50},
)Renderer is large — the Renderer struct is ~1.3MB (slug's inline vertex arrays). Always heap-allocate it with new(vkw.Renderer), never put it on the stack.
Shader compilation order — VKW's built-in shaders are embedded via #load at compile time. You must run ./build.sh shaders (or ./build.sh build, which does it automatically) before the first Odin build, or you'll get "file not found" compile errors for the .spv files.
| Platform | Architecture | Status |
|---|---|---|
| Linux | x86_64 | Tested (primary dev platform) |
| Windows | x86_64 | Tested |
| macOS | ARM64 (Apple Silicon) | Build supported via MoltenVK, community-tested |
The demo assets are included in the repo — ./build.sh run works out of the box with no extra downloads.
| Asset | Source | License |
|---|---|---|
assets/DamagedHelmet.glb |
KhronosGroup/glTF-Sample-Assets | CC BY 4.0 |
assets/BoxTextured.glb |
KhronosGroup/glTF-Sample-Assets | CC BY 4.0 |
assets/environment.hdr |
Poly Haven | CC0 |
assets/icons/*.svg |
Custom | MIT |
Note:
environment.hdris 24MB. Initial clone will take a moment on slow connections.
MIT — Copyright (c) 2026 Naughtyusername