These are the commands and checks I return to when I need a small Mesa build and a debug loop I can trust.

Build directories

A plain setup leaves driver lists on auto:

meson setup build -Dbuildtype=debug
ninja -C build

meson configure build shows the result afterwards. Once I know the API and driver, I use a narrower build directory:

meson setup build-intel -Dbuildtype=debug \
  -Dvulkan-drivers=intel -Dgallium-drivers=iris

meson setup build-mali -Dbuildtype=debug \
  -Dvulkan-drivers=panfrost -Dgallium-drivers=panfrost

Keeping a broad build beside a small one keeps configuration issues obvious.

Ninja targets

ninja -C build is incremental. Naming a target is still useful when the build contains unrelated tools or drivers.

ninja -C build -t targets all | rg 'libnir|libvulkan'
ninja -C build src/compiler/nir/libnir.a.p/nir.c.o

Object paths depend on the Meson target, so I take them from -t targets all instead of guessing.

These commands help inspect the work:

ninja -C build -n <target>
ninja -C build -t commands <target>
ninja -C build -v <target>

-t commands is useful when I need exact flags for one object.

Using the local build

Mesa's documented easy path is Meson's development environment:

meson devenv -C build vulkaninfo
meson devenv -C build glxinfo -B

It sets VK_DRIVER_FILES and adds the build's driver directories to LD_LIBRARY_PATH. Relative paths are resolved from the build directory.

For manual control, install into a disposable prefix:

export MESA_INSTALLDIR="$PWD/install"
meson setup build-install -Dprefix="$MESA_INSTALLDIR" <other options>
meson install -C build-install

VK_DRIVER_FILES="$MESA_INSTALLDIR/share/vulkan/icd.d/<driver>_icd.x86_64.json" vulkaninfo
LD_LIBRARY_PATH="$MESA_INSTALLDIR/lib64" glxinfo -B

The library directory may be lib rather than lib64. I check the selected implementation in vulkaninfo or glxinfo -B.

DRI_PRIME_DEBUG=1 explains device selection. DRI_PRIME picks another device. MESA_LOADER_DRIVER_OVERRIDE is only for debugging.

Finding a code path

Mesa is several shared layers in one source tree. I start from an API entry point, a debug string, a stack trace, or an IR dump, then follow the boundary I care about.

rg 'vkCreateGraphicsPipelines' src/
rg 'NIR_PASS.*nir_opt_' src/
rg 'DEBUG|DUMP|VALIDATE' src/<driver>/

Searching for the literal text of a log message is often faster than guessing the subsystem. Generated files can hide definitions, so I also search build/.

Debug flags

Mesa has common controls such as MESA_DEBUG, MESA_LOG_FILE, NIR_DEBUG, and GALLIUM_HUD, plus driver-specific variables.

NIR_DEBUG=help <small-reproducer>
GALLIUM_HUD=help <small-reproducer>
rg 'getenv\(|debug_get_flags|_DEBUG' src/<driver>/

Many driver variables also accept help. I start with the smallest reproducer, redirect noisy output, and keep the full environment with the result.

For compiler work, I compare before and after at the earliest IR boundary. For runtime problems, I separate loader selection, CPU work, submission, and GPU execution.

Sanitizer builds

Undefined behaviour and memory errors on the CPU side can look like driver or GPU failures. Mesa's debugging guide suggests UBSan and thread sanitizer. I also keep an ASan/UBSan build:

meson setup build-san -Dbuildtype=debugoptimized \
  -Db_sanitize=address,undefined <driver options>
ninja -C build-san
meson devenv -C build-san <small-reproducer>

meson setup build-tsan -Dbuildtype=debugoptimized \
  -Db_sanitize=thread <driver options>
ninja -C build-tsan
meson devenv -C build-tsan <small-reproducer>

debugoptimized keeps debug information and assertions. I still reduce a sanitizer report to a small reproducer before treating the first stack trace as the cause.

Bisecting regressions

If the same reproducer is good at an older commit and bad now, git bisect is usually better than reading the commits in between:

git bisect start <bad-commit> <good-commit>
git bisect run ./repro.sh
git bisect reset

The script must make its result mechanical: 0 for good, 1–127 except 125 for bad, and 125 when the commit cannot be tested. It should build only what is needed.

The loop

The loop I try to keep is: reproduce, capture one boundary, change one thing, rebuild the narrowest target, and compare. A known-good build or worktree helps.

Build options and debug flags are developer interfaces. These notes describe the method I use; check meson configure, the current documentation, and the source tree for the exact names in your checkout.