Daxa 3.6 is now available Learn more

Vulkan for Modern GPUs

Daxa is a Vulkan-powered GPU abstraction purpose-built for modern graphics cards. It keeps Vulkan's explicit control but drops the parts that stopped paying off on modern hardware. The result is less code and fewer places to get something wrong.

Batteries included

Everything raw Vulkan makes you write by hand, handled for you.

Daxa keeps Vulkan's control but cuts the busywork modern GPUs don't need. Less code, fewer mistakes.

Fully bindless

Access buffers, images, samplers, and acceleration structures by ID or pointer, on the host and inside shaders alike. Daxa removes most of the descriptor and binding boilerplate that Vulkan normally requires.

Daxa
// host: pass the buffer's address straight to the shader
recorder.push_constant(MyPush{
    .vertices = device.get_buffer_device_address(buffer).value(),
});

// main.glsl: read it like a pointer, no bindings
DAXA_DECL_PUSH_CONSTANT(MyPush, push)
void main() {
    MyVertex v = deref_i(push.vertices, gl_VertexIndex);
}

Modern GPUs only

Daxa targets Turing and newer, RDNA3 and newer, and Intel Arc. It abstracts render passes and uses a general image layout, so you never hand-manage layout transitions.

Daxa
// images use a general layout everywhere, so the
// default view works for sampling and storage alike
daxa::ImageId image = device.create_image({
    .format = daxa::Format::R8G8B8A8_UNORM,
    .size = {1920, 1080, 1},
    .usage = daxa::ImageUsageFlagBits::SHADER_SAMPLED |
             daxa::ImageUsageFlagBits::SHADER_STORAGE,
    .name = "render target",
});
daxa::ImageViewId view = image.default_view();

Shader integration

Daxa ships a shader build system with #include resolution, hot reloading, and SPIR-V caching, plus GLSL and Slang headers. Define a struct once in a shared .inl file and use it from both C++ and your shaders.

Daxa
// shared.inl: one definition, used by C++ and GLSL
#include <daxa/daxa.inl>
struct MyVertex {
    daxa_f32vec3 position;
    daxa_f32vec2 uv;
};
DAXA_DECL_BUFFER_PTR(MyVertex)

// host: edit a shader and it recompiles while running
pipeline_manager.reload_all();

TaskGraph

You declare what each task reads and writes. TaskGraph generates the barriers, reorders passes to reduce how many it needs, and aliases transient memory. Record the graph once, then run it as many times as you like.

Daxa
// say what each task touches; the graph does the sync
tg.add_task(daxa::Task::Compute("blur")
    .writes(image)
    .executes(blur));

tg.add_task(daxa::Task::Raster("present")
    .reads(image)
    .executes(present));
Side by side

The same frame, a fraction of the code.

Same behaviour on modern GPUs. The difference is everything Vulkan asks you to spell out by hand that Daxa already knows.

Bind a resource

Vulkan
// Vulkan: expose one storage buffer to a shader
VkDescriptorSetLayoutBinding bind{
    .binding = 0,
    .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
    .descriptorCount = 1,
    .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
};
VkDescriptorSetLayoutCreateInfo lci{
    .bindingCount = 1, .pBindings = &bind,
};
vkCreateDescriptorSetLayout(device, &lci, nullptr, &layout);

VkDescriptorPoolSize size{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 };
VkDescriptorPoolCreateInfo pci{
    .maxSets = 1, .poolSizeCount = 1, .pPoolSizes = &size,
};
vkCreateDescriptorPool(device, &pci, nullptr, &pool);

VkDescriptorSetAllocateInfo ai{
    .descriptorPool = pool, .descriptorSetCount = 1, .pSetLayouts = &layout,
};
vkAllocateDescriptorSets(device, &ai, &set);

VkDescriptorBufferInfo info{ buffer, 0, VK_WHOLE_SIZE };
VkWriteDescriptorSet write{
    .dstSet = set, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
    .pBufferInfo = &info,
};
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);

vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
                        pipe_layout, 0, 1, &set, 0, nullptr);
Daxa
// Daxa: hand the shader a pointer. That is the binding model.
recorder.push_constant(MyPush{
    .vertices = device.get_buffer_device_address(buffer).value(),
});

// main.glsl
DAXA_DECL_PUSH_CONSTANT(MyPush, push)

void main() {
    MyVertex v = deref_i(push.vertices, gl_VertexIndex);
}

Synchronize a pass

Vulkan
// Vulkan: barrier a compute write before a fragment read
VkImageMemoryBarrier2 barrier{
    .srcStageMask  = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
    .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
    .dstStageMask  = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
    .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT,
    .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
    .newLayout = VK_IMAGE_LAYOUT_GENERAL,
    .image = image,
    .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
VkDependencyInfo dep{
    .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier,
};
vkCmdPipelineBarrier2(cmd, &dep);
// ...and again for every resource, on every pass, every frame.
Daxa
// Daxa: declare intent, TaskGraph emits the barriers
tg.add_task(daxa::Task::Compute("blur")
    .writes(image)
    .executes(blur));

tg.add_task(daxa::Task::Raster("present")
    .reads(image)
    .executes(present));
// Barriers and reordering: generated from the reads/writes.
Built with Daxa

Renderers, engines and tools that already use it.

Timberdoodle

Research graphics engine on Daxa featuring virtual shadow maps, a GPU-driven meshlet visbuffer pipeline, and probe-based GI.

GVOX Engine

A cross-platform voxel engine for artists and game devs. WIP, C++/GLSL on Vulkan.

Nyx

Real-time Vulkan rendering playground in C++23 on Daxa, with PBR, shadows, SSAO, SSR, bloom, and volumetrics.

Tenebris

Atmosphere-rendering thesis code rewritten in the Daxa Vulkan library to test its features. C++/GLSL.

Foundation

WIP game engine with a Nanite-style virtual geometry renderer (trillions of triangles): GPU-driven, bindless, meshlets, software raster, and streaming. C++/Slang.

Desktop Shadertoy

Desktop client for Shadertoy, lets you run shaders locally and debug them in tools like RenderDoc/NSight. C++/Vulkan.

VoxyEnginePlus

C++ rewrite of PrismEngine on Daxa, aimed at efficient path-traced micro-voxels.

TASBox

Physics sandbox game on a custom engine. Make games and addons via an extensive scripting API, or just mess around with friends.

cube-tracing

WIP real-time hardware ray-traced path tracer for voxel cubes on Daxa, with ReSTIR, emissive voxels, and MagicaVoxel loading. C++/GLSL.

soc_real_time_renderer

Real-time 3D renderer built for a high school competition, packing deferred rendering, SSAO, TAA, shadows, clouds, and atmospheric scattering. C++.

Mandelbrot

Example app rendering a Mandelbrot fractal via compute shader, demonstrating Daxa's TaskGraph and hot-reloadable pipelines. C++ with Slang.