DEV Community

Cover image for Why Figma's new shader effects don't render in Electron on Linux and the GaneshGL + interop fix
Borys Kharchenko
Borys Kharchenko

Posted on

Why Figma's new shader effects don't render in Electron on Linux and the GaneshGL + interop fix

Figma's Config 2026 shader fills (dithering, blur, frosted glass, fractal noise, liquid metal) run on WebGPU. They render fine in Chrome on Linux. Wrap the same Figma in Electron on Linux + AMD, and they silently don't show up.

I chased this on an AMD Radeon 780M (Mesa RADV, Wayland/GNOME) for a self-hosted Electron Figma client. The fix turned out to be the opposite of what every "enable Skia Graphite" thread suggests. Here's the whole journey, with a reproducible probe harness.

TLDR Don't enable Skia Graphite. Use --ozone-platform=x11 --ignore-gpu-blocklist --enable-unsafe-webgpu (no --enable-skia-graphite). That keeps the fast GaneshGL raster backend while turning on webgpu_on_vk_via_gl_interop, which is what actually lets the shaders run — and without the canvas lag Graphite introduces. And apply those switches before app.ready, or Chromium ignores them.

The wrong assumption: "you need Skia Graphite"

The intuitive theory: Figma's WGSL shaders need Chromium's new Skia Graphite backend (Dawn → Vulkan), and release Electron keeps Skia on the old Ganesh-GL path, so shaders can't reach the GPU.

So I built a probe — a bare Electron BrowserWindow that renders a WebGL triangle + a WebGPU clear pass, then dumps app.getGPUFeatureStatus(), getGPUInfo(), the WebGL renderer string, and the WebGPU adapter — and ran it across a flag matrix on multiple Electron versions. (Harness + raw dumps in the gist: [LINK TO GIST].)

First gotcha: getGPUFeatureStatus() reports "everything software/off" if you read it at whenReady — the GPU process hasn't reported yet. Read it after the window has actually rendered.

Second gotcha — switch vs. feature: --enable-features=SkiaGraphite hits a guard:

gpu/config/gpu_finch_features.cc:650]
  Enabling Graphite on a not-yet-supported platform is disallowed for safety
Enter fullscreen mode Exit fullscreen mode

That guard is about Wayland ozone, not Linux in general. The dedicated switch --enable-skia-graphite bypasses it, and you need X11 ozone. Minimal recipe that actually flips Graphite on (verified against real Chromium 149 → Skia Backend: GraphiteDawnVulkan):

electron --ozone-platform=x11 --enable-skia-graphite
Enter fullscreen mode Exit fullscreen mode

It worked. chrome://gpu showed Skia Backend: GraphiteDawnVulkan. Victory?

The catch: Graphite is slow here

With Graphite on, panning the canvas lagged. Noticeably. And the same flags in raw Chromium lagged identically — so it's not an Electron artifact, it's GraphiteDawnVulkan on an integrated GPU + Mesa being heavy for this workload.

So I had working shaders but worse performance. Toggling it off restored speed but killed the shaders. Dead end — until an accident.

The accident: a third, better state

I turned the in-app toggle off, the app asked to restart, I clicked restart — and the shaders kept working, with the good performance back. chrome://gpu said Skia Backend: GaneshGL (Graphite off!) but WebGPU interop: Hardware accelerated.

Then I fully quit and cold-started — shaders gone, interop off again.

I saved all three chrome://gpu dumps. The Command Line field (chrome://gpu prints the real process argv) told the story:

State ozone skia-graphite ignore-blocklist unsafe-webgpu Skia Backend interop shaders perf
1 toggle ON x11 yes yes yes GraphiteDawnVulkan off 🐢 lag
2 toggle OFF + in-app restart x11 no yes yes GaneshGL on ⚡ fast
3 toggle OFF + cold start wayland no no no GaneshGL off fast

State 2 is the sweet spot: GaneshGL (fast) + webgpu_on_vk_via_gl_interop (shaders work). It appeared by accident because app.relaunch() re-launches with the previous process's argv (note the --ozone-platform=x11 that showed up duplicated), carrying the unlocked-GPU flags forward — but the now-toggled-off code path didn't re-add --enable-skia-graphite.

Pinning down the real recipe

Back to the probe to bisect what controls webgpu_on_vk_via_gl_interop:

flags backend interop webgpu adapter
x11 + ignore-blocklist + unsafe-webgpu GaneshGL on ✅ deviceOk
+ enable-skia-graphite Graphite off
x11 + unsafe-webgpu (no blocklist bypass) GaneshGL off
x11 + ignore-blocklist (no unsafe-webgpu) GaneshGL on ❌ no adapter

So:

  • --ignore-gpu-blocklist is required to flip interop on.
  • --enable-unsafe-webgpu is required to get a real WebGPU adapter (the shaders' device).
  • --enable-skia-graphite is harmful — it switches to the slow Graphite backend and turns interop off.

The recipe:

electron --ozone-platform=x11 --ignore-gpu-blocklist --enable-unsafe-webgpu
Enter fullscreen mode Exit fullscreen mode

Skia Backend: GaneshGL, webgpu_on_vk_via_gl_interop: enabled, a real hardware WebGPU device (AMD RDNA-3, shader-f16/subgroups), fast canvas, working shaders, zero warnings.

It works on native Wayland too, but Wayland + ignore-gpu-blocklist enables Vulkan compositing, which logs '--ozone-platform=wayland' is not compatible with Vulkan and risks instability. X11 (under XWayland) is clean — the only cost is losing native Wayland niceties (fractional scaling, per-monitor DPI).

Side note on Chromium's issue 442791440: webgpu_on_vk_via_gl_interop is listed as a disabled feature by default for AMD+Mesa, but in practice it flips to "Hardware accelerated" once the blocklist is bypassed and Graphite isn't forced. Useful data point if you're tracking that bug.

The bug that made it all flaky: switches applied too late

Why did the recipe only work after an accidental app.relaunch(), never on a clean start? Because app.commandLine.appendSwitch(...) only takes effect if it runs synchronously, before app.ready. The main process did:

async function start() {
  await storage.initialize();   // app.ready fires during this await
  ...
  new App();                    // ← appendSwitch('ozone-platform','x11'), ('enable-skia-graphite'), ...
}                               //    too late — Chromium already initialized the GPU process
start();
Enter fullscreen mode Exit fullscreen mode

The app even logged "Skia Graphite enabled" — because the code path ran — while chrome://gpu reported plain GaneshGL, because the switches were ignored. A trivial await Promise.resolve() before the appendSwitch calls is enough to lose them.

Fix: apply GPU switches at the top level of your main entry, before any await. (Read your settings synchronously with fs.readFileSync if you must — that's exactly what you'd already do for --remote-debugging-port.)

Takeaways

  • For Figma's WebGPU shader fills in Electron on Linux/AMD, the path is GaneshGL + webgpu_on_vk_via_gl_interop, not Skia Graphite.
  • --enable-skia-graphite is a trap on integrated GPUs: it's slower and disables the interop bridge.
  • The interop switch is --ozone-platform=x11 --ignore-gpu-blocklist --enable-unsafe-webgpu.
  • Apply GPU command-line switches before app.ready, or they're silently dropped.
  • getGPUFeatureStatus() lies if you read it too early — read it after first paint.

Reproducible harness, all three chrome://gpu dumps, and the full flag matrix: https://gist.github.com/arximus88/f8d93ae1568a5c49e4206250f323b178.
Project: https://github.com/arximus88/figma-linux-next.

Tested on Electron 42.0.1 / 42.3.0 / 43.0.0-beta.6 (Chrome 148–150), AMD Radeon 780M, Mesa 26.1.2 RADV, GNOME/Wayland.

Top comments (0)