Table of Contents
GPU Rendering Artifacts on ThinkPad X220 with Hyprland — Diagnosis & Fix
Hardware & Software Context
| — | — |
| Machine | Lenovo ThinkPad X220 (4291SWP) |
| GPU | Intel HD Graphics 3000 — Sandy Bridge, Gen 6 |
| Kernel driver | i915 |
| Mesa driver | crocus (Gallium3D for Intel Gen 4–8) |
| Wayland compositor | Hyprland (uses Aquamarine as DRM backend) |
| Display | Internal LVDS-1, 1366×768 |
Sandy Bridge (2010–2011) is the oldest Intel architecture still supported by Mesa's Gallium3D stack. It cannot use Vulkan (requires Gen 7+), cannot use the iris driver (requires Gen 8+), and only supports OpenGL ES 3.0 — not 3.2. Any rendering stack must go through crocus.
Symptoms
- Dropdown/popup artifacts in browsers (Firefox, Librewolf): bookmark menus, context menus, and form dropdowns show flickering garbage pixels or appear partially rendered.
- Waybar icon artifacts: hovering over status bar icons produces visual corruption in the hover region.
- VS Code fonts completely garbled: all text in VS Code is illegible — random pixel patterns instead of glyphs.
- System-wide nature: the artifacts are not confined to one application; any GPU-composited surface can show corruption.
How to Investigate This Class of Issue
The following steps, taken in order, efficiently isolate the cause of system-wide GPU rendering corruption on a Hyprland/Wayland system.
Step 1 — Gather system context
omarchy debug --no-sudo --print
Note the GPU, Mesa version, Hyprland version, and kernel version. On a Sandy Bridge machine: confirm the driver is crocus and the DRI path is /usr/lib/dri/crocus_dri.so.
Step 2 — Check recent package updates
grep -E "(mesa|hyprland|xwayland|vulkan|electron|code)" /var/log/pacman.log | tail -60
The most relevant packages for Wayland compositor rendering are: mesa, hyprland, xorg-xwayland, and libdrm. Note which versions were installed and when symptoms began.
Step 3 — Read the live Hyprland log
cat /run/user/$(id -u)/hypr/*/hyprland.log
Look for:
- EGL context creation errors — eglCreateContext errored out with EGL_BAD_MATCH: Sandy Bridge only supports GLES 3.0; any code path requesting GLES 3.2 will fail. A failed context without a successful fallback means a renderer is degraded or absent.
- GBM buffer format — format XR24 with modifier 0x...: the modifier field shows whether buffers are LINEAR (modifier 0x0) or tiled (e.g. X_TILED, modifier 0x100000000000001). Tiled formats on Sandy Bridge can corrupt popup surfaces.
- DRM commit errors — drm: Cannot commit when a page-flip is awaiting: frame timing failures; indicates Hyprland is submitting frames faster than the display can flip.
- Renderer selected — confirms which GPU and GLES version are active.
Step 4 — Check Aquamarine environment variable support
Hyprland's DRM backend (Aquamarine) respects specific environment variables. Extract the ones it knows about:
strings /usr/lib/libaquamarine.so* | grep -E "^AQ_" | sort -u
Key variables: AQ_NO_MODIFIERS, AQ_NO_ATOMIC, AQ_FORCE_LINEAR_BLIT, AQ_DRM_DEVICES.
Step 5 — Inspect the Mesa driver architecture
# Check if crocus is a standalone library or a symlink to a unified loader file /usr/lib/dri/crocus_dri.so # Check for a shared libgallium — if present, ALL Gallium drivers live here ls -lh /usr/lib/libgallium*.so
In Mesa 26.0.x, crocus_dri.so was a standalone self-contained shared library. In Mesa 26.1.x, all Gallium3D drivers were consolidated into libgallium-<version>.so, with crocus_dri.so becoming a thin stub (crocus_dri.so → libdril_dri.so, ~95 KB loader). This architectural change is a common regression vector for older hardware.
Step 6 — Check i915 kernel module parameters
cat /etc/modprobe.d/i915.conf ls /sys/module/i915/parameters/
Sandy Bridge is sensitive to several i915 features. The following should typically be disabled for stability:
| Parameter | Recommended | Reason |
| — | — | — |
enable_psr | 0 | Panel Self Refresh causes display corruption on LVDS |
enable_fbc | 0 | Framebuffer Compression has known Sandy Bridge bugs |
enable_dc | 0 | Display power-saving can interfere with DRM commits |
Step 7 — Check Mesa shader cache
du -sh ~/.cache/mesa_shader_cache
After a Mesa version upgrade, stale cached shader binaries from the previous version can cause rendering failures. Clear the cache after any Mesa upgrade:
rm -rf ~/.cache/mesa_shader_cache
Step 8 — Confirm the fix candidate by checking cached packages
ls /var/cache/pacman/pkg/mesa-*.zst
If an older working Mesa version is in the package cache, it can be reinstated quickly without downloading. This is the fastest route to confirming a Mesa regression: downgrade, observe whether artifacts disappear, and if so, pin the working version.
Root Causes Found (This System, Mesa 26.1.1 / Hyprland 0.55.2)
Root Cause 1 — Mesa 26.1.1 crocus regression (primary, system-wide)
Mesa 26.1.0 introduced a major architectural refactor: all Gallium3D drivers were merged from individual shared libraries into a single libgallium-<version>.so. The crocus_dri.so driver became a ~95 KB stub loader (libdril_dri.so) that dispatches into the 52 MB monolithic library.
Mesa 26.0.6: crocus_dri.so — standalone, ~15 MB, self-contained Mesa 26.1.1: crocus_dri.so → libdril_dri.so (95 KB) → libgallium-26.1.1.so (52 MB)
The refactoring introduced a regression in the crocus EGL/GLES rendering path for Sandy Bridge (Gen 6). Because Hyprland's Aquamarine compositor backend uses crocus for all compositor rendering via EGL (every window's contents are textured and blended by crocus), the regression corrupts every composited surface — producing system-wide artifacts.
This is confirmed by: (a) artifacts appearing immediately after the Mesa 26.0.6 → 26.1.1 upgrade, (b) artifacts disappearing after downgrading back to 26.0.6.
Root Cause 2 — GBM buffer modifiers not disabled (popup artifacts)
Hyprland's Aquamarine backend, by default, allocates GBM display buffers using hardware-optimal tiled formats (e.g. X_TILED, modifier 0x100000000000001) for better GPU scan-out performance. On Sandy Bridge, the crocus driver can corrupt new popup surfaces (dropdowns, tooltips) when they first appear in a tiled buffer — the compositor reads back the buffer before the GPU has flushed the tile swizzle.
The fix is to force linear buffer allocation via the Aquamarine environment variable AQ_NO_MODIFIERS=1.
Verification: after setting this variable, the Hyprland log should show modifier 0x0 : LINEAR for all GBM buffer allocations.
Root Cause 3 — VS Code / Electron GPU rendering on Wayland
Omarchy sets ELECTRON_OZONE_PLATFORM_HINT=wayland globally, causing VS Code (Electron) to run natively on Wayland and use the GPU for font rendering. Combined with the crocus regression and Sandy Bridge's GLES 3.0 limitation, Electron's GPU text rendering pipeline produces garbled glyphs.
This is application-scoped and does not require fixing the compositor — it is resolved independently with --disable-gpu in VS Code's launch flags.
Fix Applied
1. Downgrade Mesa 26.1.1 → 26.0.6
The old package was still in the pacman cache:
sudo pacman -U \ /var/cache/pacman/pkg/mesa-1:26.0.6-1-x86_64.pkg.tar.zst \ /var/cache/pacman/pkg/vulkan-mesa-implicit-layers-1:26.0.6-1-x86_64.pkg.tar.zst
2. Pin Mesa in pacman.conf
Add to /etc/pacman.conf under [options]:
IgnorePkg = mesa vulkan-mesa-implicit-layers
This prevents omarchy update / pacman -Syu from re-upgrading Mesa until a release that fixes the crocus regression is confirmed working.
3. Force linear GBM buffer allocation
In ~/.config/hypr/hyprland.conf:
# Force Aquamarine to allocate GBM buffers in linear format. # Sandy Bridge (crocus) corrupts popup surfaces with tiled modifiers. env = AQ_NO_MODIFIERS,1 # Force linear blit path in Aquamarine's renderer (belt-and-suspenders). env = AQ_FORCE_LINEAR_BLIT,1
4. Disable hardware cursor
In ~/.config/hypr/looknfeel.conf:
cursor { no_hardware_cursors = true }
Sandy Bridge's i915 hardware cursor has known glitches with the crocus Wayland stack.
5. Disable VS Code GPU acceleration
Create ~/.config/code-flags.conf:
--disable-gpu
6. Kernel module parameters for i915
In /etc/modprobe.d/i915.conf:
options i915 enable_psr=0 options i915 enable_fbc=0 options i915 enable_dc=0
Requires a reboot to take effect (or modprobe -r i915 && modprobe i915, which is only possible when Hyprland is not running).
7. Clear Mesa shader cache
rm -rf ~/.cache/mesa_shader_cache
Run this after any Mesa version change.
8. Restart Hyprland session
A full session restart (log out → log back in) is required after a Mesa downgrade. Running processes retain the old Mesa shared library (libgallium-26.1.1.so) in memory; only a fresh Hyprland process loads the downgraded version.
What to Monitor Going Forward
- Mesa 26.1.x patch releases: Check the Mesa changelog for crocus/Sandy Bridge fixes. When a release notes a crocus fix, remove the
IgnorePkgline from/etc/pacman.confand upgrade. - Mesa bug tracker: The regression can be reported/tracked at gitlab.freedesktop.org/mesa/mesa — search for issues tagged
crocusorGen6. - Hyprland 0.55.x atomicity: The
AQ_NO_ATOMIC=1flag in Aquamarine was tested and found to cause additional errors on Sandy Bridge (Aquamarine's legacy DRM mode reports missing CRTC/encoder IDs for all disconnected ports). It should not be set on this hardware.
Quick Diagnostic Checklist for the Same Issue
If you are on a ThinkPad X220 (or any Sandy Bridge machine) with Hyprland and see system-wide rendering artifacts after a system update, run through this in order:
pacman -Q mesa— what Mesa version is installed?cat /run/user/$(id -u)/hypr/*/hyprland.log | grep -E "ERR|modifier|GLES"— any EGL failures or XTILED buffers? 3.file /usr/lib/dri/crocus_dri.so— is it a real library or a symlink tolibdril_dri.so? 4.ls -lh /usr/lib/libgallium*.so— does a monolithiclibgalliumexist? 5.ls /var/cache/pacman/pkg/mesa-*.zst— is an older Mesa version cached for rollback? 6. SetAQ_NO_MODIFIERS=1in hyprland.conf and reload — do popup artifacts improve? 7. If all else fails, downgrade Mesa and pin it.
