# 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 ```bash 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 ```bash 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 ```bash 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: ```bash 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 ```bash # 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-.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 ```bash 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 ```bash 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: ```bash rm -rf ~/.cache/mesa_shader_cache ``` ### Step 8 — Confirm the fix candidate by checking cached packages ```bash 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-.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: ```bash 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]`: ```ini 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`: ```ini # 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`: ```ini 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 ```bash 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](https://docs.mesa3d.org/relnotes.html) for crocus/Sandy Bridge fixes. When a release notes a crocus fix, remove the `IgnorePkg` line from `/etc/pacman.conf` and upgrade. - **Mesa bug tracker**: The regression can be reported/tracked at [gitlab.freedesktop.org/mesa/mesa](https://gitlab.freedesktop.org/mesa/mesa) — search for issues tagged `crocus` or `Gen6`. - **Hyprland 0.55.x atomicity**: The `AQ_NO_ATOMIC=1` flag 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: 1. `pacman -Q mesa` — what Mesa version is installed? 2. `cat /run/user/$(id -u)/hypr/*/hyprland.log | grep -E "ERR|modifier|GLES"` — any EGL failures or X_TILED buffers? 3. `file /usr/lib/dri/crocus_dri.so` — is it a real library or a symlink to `libdril_dri.so`? 4. `ls -lh /usr/lib/libgallium*.so` — does a monolithic `libgallium` exist? 5. `ls /var/cache/pacman/pkg/mesa-*.zst` — is an older Mesa version cached for rollback? 6. Set `AQ_NO_MODIFIERS=1` in hyprland.conf and reload — do popup artifacts improve? 7. If all else fails, downgrade Mesa and pin it.