I use this map when I need to decide whether a failure belongs to an API driver, a compiler backend, the kernel, or the hardware. I learned the boundaries by moving between Intel, NVIDIA, and Mali systems rather than by treating “the graphics driver” as one program.

The compressed version

For the questions I usually care about, I reduce the stack to three pieces: a kernel driver, a userspace API driver, and a shader compiler.

Vulkan application
       │ SPIR-V + pipeline state
       ▼
Mesa Vulkan driver
       ├─ SPIR-V → NIR → target backend → GPU machine code
       └─ commands and resources → DRM kernel driver → GPU

On machines I use, the abstract names become concrete:

RPL-S       Vulkan → ANV   → NIR → BRW       → i915
AD107       Vulkan → NVK   → NIR → NAK       → nouveau
RK3588S     Vulkan → PanVK → NIR → Bifrost/Kraid → Panthor

The arrows are also debugging boundaries. A wrong NAK instruction is not fixed in Nouveau; a failed dma-buf negotiation is not fixed in the shader compiler.

Kernel and userspace

The kernel driver owns work that requires kernel authority: memory management, command submission from multiple processes, synchronization, power management, and recovery after a hang. Display may live in the same driver or in a separate display pipeline, which matters on SoCs such as RK3588S where the Mali GPU is not the display controller.

A Mesa userspace driver implements an API such as Vulkan or OpenGL. It consumes application state, builds pipelines and command buffers, manages resources, and eventually talks to the matching kernel driver through ioctls when work is submitted. The API-facing driver and kernel driver are separate pieces even when they support the same GPU.

The compiler path

Vulkan applications normally provide shaders as SPIR-V. Mesa drivers translate SPIR-V to NIR, run shared and driver-specific lowerings and optimizations, then hand the result to a target backend. That backend selects instructions, allocates registers, schedules instructions, and emits machine code for the selected GPU.

The shared IR is important, but the end of the pipeline is hardware-specific. GPU generations differ in instructions, encodings, hazards, register files, and execution details, so machine code for one target is not a portable GPU binary.

Why compilation happens at runtime

With a microcontroller I can cross-compile for a fixed target and flash the result. Vulkan keeps SPIR-V portable and lets the driver compile for the GPU and driver implementation actually present on the machine. That is why the same captured shader can exercise BRW through an Intel stub, while proving an NAK encoding still requires an NVIDIA decoder or real NVIDIA hardware.

Applications can build pipelines ahead of their first draw, reuse pipeline caches, or compile work in the background. When compilation still lands on a latency-sensitive path, users see the familiar shader or pipeline-compilation stutter. “The first time a shader is seen” is a useful approximation, not the whole lifecycle.

Where I stop the map

I leave most display, WSI, firmware, memory-residency, and shader-cache details out of this first map. I add one of those paths only after the observable points there; otherwise “understanding the whole stack” becomes a way to avoid reducing the bug.