This is the loop I assembled while working on BRW, Jay, NAK, and driver-side experiments. It is not a general Mesa installation guide. The point is to know which checkout produced a result, which driver was loaded, and what each test can actually establish.

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 \
  -Dtools=intel,drm-shim

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

ninja -C build-intel
ninja -C build-mali

The Intel build contains ANV, Iris and the shared Intel compiler, including BRW and Jay. tools=intel brings the Intel tools directory into the build; drm-shim enables its fake-device support. Both are needed for intel_stub_gpu.

I keep broad and driver-specific builds separate.

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 prints the exact flags for a target.

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.

The same environment can initialize ANV as a PTL device without PTL hardware:

meson devenv -C build-intel \
  intel_stub_gpu -p ptl -- vulkaninfo --summary

meson devenv supplies both intel_stub_gpu on PATH and the generated Intel ICD through VK_DRIVER_FILES. The stub is a compiler and driver-initialization tool. It does not execute shaders.

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.

Shader corpora

Shader-db contains two related workflows. Its run binary feeds captured GL shaders through Iris and prints compiler statistics for report.py. Its fossils/ directory contains Vulkan pipeline databases; Fossilize replays those through ANV and writes CSV for report-fossil.py.

cd ../shader-db
make

cd ../Fossilize
git submodule update --init
cmake --workflow --preset linux-release

Fossilize's CLI build needs its SPIR-V and JSON submodules. Run the following from the shader-db directory:

export PATH="$PWD/../Fossilize/out/build/linux-release/cli:$PATH"

meson devenv -C ../mesa/build-intel \
  intel_stub_gpu -p ptl -- \
  env MESA_SHADER_CACHE_DISABLE=true \
  "$PWD/fossil_replay.sh" "$PWD/fossils" "$PWD/brw.csv" --num-threads 1

meson devenv -C ../mesa/build-intel \
  intel_stub_gpu -p ptl -- \
  env MESA_SHADER_CACHE_DISABLE=true INTEL_JAY=all \
  "$PWD/fossil_replay.sh" "$PWD/fossils" "$PWD/jay.csv" --num-threads 1

./report-fossil.py brw.csv jay.csv

The path is .foz → Fossilize → ANV → BRW or Jay → CSV. INTEL_JAY=all selects Jay for every supported stage. The helper uses a separate replay process per database and combines their CSV output.

The classic Iris runner currently prints BRW statistics but no equivalent report.py lines when Jay is selected. For Jay/BRW comparisons I use the ANV/Fossilize path.

Reading the statistics

report-fossil.py treats a subgroup-size change specially: for that executable it reports the subgroup change and discards the other metric deltas. For a controlled compute comparison, INTEL_SIMD_DEBUG=cs16 can force the allowed compute mode to SIMD16, but it changes compiler policy and should not be part of an ordinary baseline.

A missing backend statistic may arrive as zero and appear as +inf% or -inf%. That is not an infinite performance change. Check the CSV columns and raw rows before interpreting it.

Instruction count, code size, estimated cycles, registers, spills and scratch are static compiler statistics. With a stub GPU they can identify code-quality differences, not rendering correctness or real GPU speed. Compile-time measurements also need cache-disabled paired runs, fixed inputs and thread count, and several repetitions; process startup and CPU scheduling remain noise.

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.

Choosing the proof

Different projects forced me to use different stopping points. Shader-db was enough to show that an ACO combine never appeared in my corpus. An Intel stub let me compare BRW and Jay compiler output without the target GPU. The NAK PLOP3 .SIGN work needed nvdisasm for encoding agreement and AD107 hardware for execution semantics. None of those tools substitutes for the others.

The loop

I try to keep one question per run: reproduce, capture the earliest useful boundary, change one thing, rebuild the narrowest target, and compare. If I cannot say what a passing command rules out, the command is probably too broad.

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.