Skip to content

Vulkan backend

The backend is the layer that drives the PC's graphics card through Vulkan. It opens the card once, uploads what a program needs, runs it, and copies the results back. For example, a compute run records one command list, submits it, waits until the card is done, and reads the output buffer.

flowchart TB
    DEV["gpu::device()<br/>The one connection to the card"] --> Q["One queue for drawing and compute<br/>One command pool"]
    Q --> SUB["Submit<br/>One command list and one done-flag, locked"]
    SUB --> CP["ComputePipeline<br/>A compute shader, ready to run"]
    SUB --> GP["GraphicsPipeline<br/>Vertex and pixel shaders, ready to draw"]
    CP --> D0["Descriptors<br/>The numbered data slots, sets 0 and 1"]
    GP --> D0
    D0 --> HB["HostBuffer<br/>Constants, emulated memory window, vertices"]
    D0 --> HI["HostImage<br/>Textures and writable images"]
    GP --> RT["RenderTarget<br/>The image a draw paints, plus depth"]

What the backend owns, from the device down to the buffers a shader reads.

All work goes through one queue, the place work is handed to the card. Each submit uses one command buffer, a recorded list of card commands, and one fence, a flag the card raises when it finishes. Every submit waits on its fence and copies results to host memory before it returns.

What are the parts?

Part File Role
VkState gpu/mod.rs The Vulkan instance, the chosen card, its queue and command pool, and which optional features it has
HostBuffer gpu/compute.rs A buffer the CPU can read and write; cached memory preferred, flushed and refreshed explicitly
HostImage gpu/compute.rs A 2D image with all its mip levels (smaller copies), uploaded through a staging buffer
Descriptors gpu/compute.rs The layout of numbered data slots: set 0 for compute and vertex shaders, set 1 for pixel shaders
ComputePipeline gpu/compute.rs A compiled compute shader; VkState::dispatch records, submits and waits
GraphicsPipeline gpu/graphics.rs Compiled vertex and pixel shaders for one colour format, with optional depth
RenderTarget gpu/graphics.rs Offscreen colour and depth images, read back after each draw

Which card features does it ask for?

Choice Why
Vulkan 1.3, the first card with a queue for both drawing and compute Dynamic rendering: drawing straight into images without render pass objects
16-bit and 64-bit floats, 64-bit integers, when present SASS has instructions on all of these
64-bit atomics on buffers and shared memory, when present For SASS's 64-bit ATOM and ATOMS; Intel lacks the shared-memory one
Compute shader derivatives TMML, which asks which mip level a texture read would use, needs them in compute
Keep signed zeros, infinities, NaNs and denormals at every float width Drivers fold x + -x to zero and Intel flushes tiny floats to zero; the Switch 2's GPU does neither
Graphics pipeline libraries and extended dynamic state 3, when present Pipelines link from prebuilt shader parts, and vertex stride, write mask and blending become per-draw settings
No Vulkan driver found means NoDevice Tests skip cleanly on machines without Vulkan

Which settings can change per draw?

A Vulkan pipeline is a compiled bundle of shaders and settings. Building one is slow, so Kokoromi keeps as much as possible as dynamic state, settings changed per draw for free.

Kind Settings
Dynamic, changed per draw Viewport, scissor, cull mode, front face, triangle or line type within its group, depth test, depth write, depth compare, blend constants
Dynamic when the device has the extensions above Vertex stride, colour write mask, blend enable and blend equation
Fixed, part of the engine's pipeline key Shader hashes, vertex layout, primitive group, colour write mask, blending, depth format

How does it stay fast?

  • The card connection is created once per process. Command buffers, fences, translated shaders and pipelines are reused.
  • Results are read from cached host memory, because reading uncached memory is slow.
  • The engine caches translated shaders by content, pipelines by key, and buffer copies by emulated RAM range. A copy refreshes only when its memory changed.
  • Pipelines can be built on worker threads. Default mode skips a draw whose pipeline is not ready; exact mode waits.
  • Translated SPIR-V and the driver's pipeline cache are saved to disk per game, so the next run starts warm.

Measured on an Intel Arc (MTL), p50, cargo run --release -p kokoromi-core --example perf:

Path Latency
Translate 1000 instructions 326 µs
Dispatch, fence and readback 135 µs
Draw and readback, 64x64 triangle 193 µs
1080p test frame end to end 4.2 ms
Julia demo frame end to end 4.7 ms
Pushbuffer triangle at 1080p 6.6 ms

Why does CI run on lavapipe?

Mesa's lavapipe is a Vulkan driver that runs on the CPU, so CI needs no graphics card. The Ubuntu CI job installs lavapipe and the SPIR-V tools and points Vulkan at lavapipe. It runs the whole suite, then prints the perf numbers as a baseline. Every GPU test also runs locally on Intel. Where lavapipe differs from the real chip, the test fixtures list the difference for that device.

Verified by: the Vulkan validation layer, a debug layer that reports API misuse, clean on Intel and lavapipe for dispatch, draw and depth; and the suite skipping without a Vulkan driver.

Sources: core/src/gpu/{mod,compute,graphics,engine,pipeline_cache}.rs, core/examples/perf.rs, .github/workflows/ci.yml.