"Go deserves more support in GUI development"
— r/golang, October 2024
That Reddit post hit a nerve. Hundreds of upvotes, dozens of comments echoing the same frustration: Go has world-class networking, databases, and CLI tools — but when it comes to graphics and GUI, the ecosystem says "just use Electron" or "call into C."
I'd been planning a Pure Go GPU stack for years. Spent the year before that post studying exactly what to build and how — dissecting Vulkan, Metal, DX12, reading wgpu source code, analyzing Qt and Flutter architectures. That Reddit thread was the final push.
On December 5, 2025, the first window with a triangle appeared on screen. Pure Go, zero CGO, Vulkan rendering.
Less than five months later: 790K lines of Pure Go, 13 repositories, 678 GitHub stars across the ecosystem, and people building real software on it — from Quake engines to ML frameworks.
The Numbers
| Repository | Description | Lines | Version |
|---|---|---|---|
| gg | 2D graphics (Skia-class rasterizer) | 219K | v0.43.4 |
| naga | Shader compiler (WGSL→SPIR-V/MSL/GLSL/HLSL/DXIL) | 195K | v0.17.6 |
| ui | GUI toolkit (22+ widgets, 4 themes, 97% coverage) | 171K | v0.1.14 |
| wgpu | WebGPU implementation (Vulkan/DX12/Metal/GLES/Software) | 145K | v0.26.8 |
| gogpu | Application framework, windowing, input | 50K | v0.30.0 |
| g3d | 3D rendering (scene graph, PBR, GLTF) | planned | — |
| compose | Jetpack Compose-style declarative UI | planned | — |
| systray | Cross-platform system tray | planned | — |
| + 4 more | gpucontext, gputypes, gg-pdf, gg-svg | 9K | — |
| Total | 13 repos, Pure Go, zero CGO | ~790K | — |
790K lines of Go. 577K of that is pure code (excluding blanks and comments). 421 commits in the last two months alone.
Stars across the ecosystem:
| Repo | Stars |
|---|---|
| gogpu | 251 |
| ui | 211 |
| gg | 96 |
| wgpu | 87 |
| naga | 33 |
| Total | 678 |
How It Started
The gap between Go's server capabilities and its desktop capabilities has bothered me for years. Go has net/http that scales to millions of connections. It has database libraries that handle petabytes. But ask "how do I draw a triangle?" and you get answers involving CGO, shared libraries, or wrapping Electron in a Go process.
The existing options in 2024-2025:
| Framework | Stars | Approach | The Catch |
|---|---|---|---|
| Wails | ~34K | Go backend + System WebView | Not native rendering. Your "Go app" is HTML/CSS/JS with JSON IPC. |
| Fyne | ~28K | Go + OpenGL via CGO | Requires C compiler. Custom widget look, not truly native. |
| Ebiten | ~13K | Go + OpenGL/Metal/DX (purego) | Game engine, not a GUI toolkit. No widget system. |
| Cogent Core | ~2.3K | Go + Vulkan via CGO (glfw) | CGO required. Large dependency tree. |
| Gio | ~2.1K | Pure Go, immediate mode | Small widget set. Pre-1.0 API. No retained mode. |
Every major framework either requires CGO or delegates rendering to a WebView. The only Pure Go option (Gio) is immediate-mode with a minimal widget set. Nothing offers native GPU rendering (Vulkan/Metal/DX12) without a C compiler.
I spent a year studying the problem:
- Read Rust wgpu's source code to understand how a modern WebGPU implementation works
- Studied Qt6's RHI, GTK4's GSK renderer, Flutter's Impeller to understand GUI rendering pipelines
- Analyzed Vulkan, Metal, DX12, and OpenGL ES specs to map out the abstraction boundaries
- Designed the layered architecture on paper before writing Go code
On December 5, 2025, the first window with a triangle appeared on screen — rendered through go-webgpu/webgpu (our Rust FFI bindings) and the gogpu framework. The Rust backend proved the architecture worked. Then, in parallel, naga (shader compiler) and wgpu (Pure Go WebGPU) began replacing the Rust dependencies one by one. Then came gg (2D graphics), and finally ui (GUI toolkit).
Multi-Window: The Feature That Changes Everything
Single-window apps are fine for demos. Real applications — IDEs, design tools, database clients — need multiple windows. We shipped multi-window support in gogpu v0.28.0, and it was the hardest architectural change we've made.
The Problem
Every major Go GUI framework (Fyne, Gio, Ebiten) either doesn't support multi-window or hacks it with separate processes. The reason is simple: GPU devices are expensive to create, but surfaces (swapchains) are per-window. You need to share one device across many windows without data races.
What We Built
We studied 7 frameworks — Qt6, GTK4, SDL3, winit, GLFW, Fyne, Gio — and wrote a 26-page Architecture Decision Record before writing a line of code.
┌────────────────────────────────┐
│ Shared GPU Context │
│ Instance / Adapter / Device │
│ Queue / Pipelines / Textures │
└───────┬──────────┬─────────┬───┘
│ │ │
┌───────┴───┐ ┌───┴────┐ ┌──┴─────────┐
│ Window 1 │ │Window 2│ │ Window 3 │
│ Surface │ │Surface │ │ Surface │
│ Swapchain │ │Swapch. │ │ Swapchain │
│ Callbacks │ │Callbk. │ │ Callbacks │
└───────────┘ └────────┘ └────────────┘
The API is intentionally simple:
app := gogpu.NewApp(gogpu.DefaultConfig().
WithTitle("Main Window").
WithSize(1200, 800))
// Second window — shares GPU device, gets its own surface
tools := app.NewWindow(gogpu.WindowConfig{
Title: "Tool Palette",
Width: 300, Height: 600,
})
tools.OnDraw(func(dc *gogpu.Context) {
// dc has its own surface, shared device
})
Under the hood: PlatformManager handles process-level concerns (Win32 RegisterClass, Cocoa NSApplication, X11 Display), while each PlatformWindow manages its own message pump slice, surface, and swapchain. The WindowManager tracks all windows with monotonic WindowID — stable across window recreation, serializable for event queues. Same pattern SDL3 uses with SDL_GetNextObjectID().
VSync Strategy
Primary window runs Fifo (VSync), secondary windows run Immediate. Focus changes switch the VSync mode — the window you're looking at gets smooth frames, background windows don't waste GPU cycles. This is what Qt6 does with QRhi.
Cross-Platform
| Platform | Backend | Multi-Window |
|---|---|---|
| Windows | Vulkan, DX12 | ✅ Win32 CreateWindowEx
|
| macOS | Metal | ✅ Cocoa NSWindow
|
| Linux X11 | Vulkan | ✅ XCreateWindow
|
| Linux Wayland | Vulkan | ✅ xdg_toplevel
|
All 4 platforms support multi-window with shared GPU device. EventFocus events route correctly across windows on all platforms.
Event-Driven Frame Pacing
Enterprise GUI frameworks don't render on every OS event. They render only when something visual actually changed. Our render loop had a shortcut:
if continuous || invalidated || hasEvents {
renderFrame()
}
That hasEvents flag meant every mouse move, every platform event triggered a full render cycle — even when nothing on screen changed. Moving the mouse over a static window caused unnecessary GPU work on every frame.
We researched 6 frameworks (winit, Gio, Qt6, Flutter, SDL3, Ebiten) and found they all use the same pattern: handlers decide when to invalidate, the render loop never guesses.
Now in v0.30.0:
if continuous || invalidated {
renderFrame()
}
Resize and focus events call RequestRedraw() explicitly. Mouse events reach the UI framework, which calls RequestRedraw() only when a widget's visual state actually changes.
Mouse move over static UI: 0% GPU. Hover over a button: UI calls RequestRedraw(), one frame renders. Exactly how winit and Flutter work.
The First Pure Go DXIL Generator
We wrote about this in detail, but here's the update: naga's DXIL backend now passes 94/208 DXC golden tests (45% parity with Microsoft's own compiler). The IDxcValidator pass rate is 161/170 (94.7%).
This matters because Rust naga — the reference implementation maintained by Mozilla — still doesn't have a DXIL backend. There's been an open issue since 2020. Six years later, still not implemented.
We did it in Pure Go. No LLVM, no external tools. Direct DXIL bitcode generation from our shader IR.
Yes, we know Microsoft is adding SPIR-V support to DirectX 12 in a future SDK update. But DXIL is the native DX12 shader format today, and having a direct DXIL generator means we don't depend on external toolchains or wait for Microsoft's timeline.
How the Shader Pipeline Works
WGSL source code
↓ (naga lexer + parser)
naga IR (typed, validated)
↓ (backend selection)
┌───┴───┬────┬─────┬──────┐
SPIR-V MSL GLSL HLSL DXIL
(Vulkan)(Metal)(GLES)(DX11)(DX12)
All 5 backends generate from the same IR. 330+ tests. The DXIL backend includes single-store local promotion, strength reduction, fragment signature ordering, and dead code elimination — real compiler passes, not string templating.
gogpu/ui: A Real GUI Toolkit
Most Go GUI toolkits ship a handful of basic controls and call it done. We're building something closer to Qt or Flutter — a complete widget system with theming, accessibility, and enterprise-grade architecture.
22+ widgets shipping today:
| Category | Widgets |
|---|---|
| Input | TextField, TextArea (multi-line), Checkbox, Radio, Switch, Slider, Dropdown |
| Display | Label, Image, Icon, ProgressBar, Badge, Chip, Divider, Card |
| Navigation | Button, IconButton, FAB, AppBar, TabBar |
| Layout | Column, Row, Stack, ListView, ScrollView, GridView |
| Overlay | Dialog, Snackbar, Tooltip, BottomSheet, PopupMenu |
| Advanced | TreeView, DataTable, SplitView, Docking, Menu, Toolbar |
4 design systems: Material Design 3, DevTools (JetBrains), Fluent (Microsoft), Cupertino (Apple). Full token-based theming — change every color, radius, elevation, font in one struct.
171K LOC | 55+ packages | 6,803 tests | 97%+ average coverage.
Signal-Driven Retained-Mode Compositor (ADR-007)
The latest v0.1.14 shipped the biggest architectural change in ui: a signal-driven retained-mode compositor that replaced the legacy hybrid pipeline.
The old pipeline had a fundamental conflict: CPU pixmap was retained (persistent between frames), but GPU shapes (shadows, rounded corners) were ephemeral (re-queued every frame). On frames where nothing changed, GPU shapes disappeared — visible flicker.
ADR-007 unified everything into a scene graph:
Widget tree
↓ SetNeedsRedraw() (per-widget dirty flag)
Scene.Encoding (compact display list: 9-25 bytes/command)
↓ RepaintBoundary (cached scene per subtree)
scene.Renderer (auto GPU/CPU selection, 64×64 tile grid)
↓ FlushGPUWithView (single render pass → swapchain)
Every widget records into a scene.Scene display list instead of drawing to a pixmap. RepaintBoundary caches scene encodings per subtree — when a spinner animates, only its 48×48 tile re-renders, not the full window. Reactive signals (coregx/signals) trigger SetNeedsRedraw() on exactly the widgets that changed.
Results:
- Taskmanager example GPU: 7-18% → 0-1%
- IDE hover lag: eliminated
- Gallery flickering: fixed
- Full DrawTree via GPU pipeline every frame — no retained CPU pixmap
This is still a work in progress. Complex animated widgets like classic spinners need additional optimization — the present path still does a full-surface render pass even when only a 48×48 pixel region changed. Damage-rect passthrough and sub-region compositing are the next steps.
More Architecture
- Reactive state via signals (Signal, Computed, Effect, Binding)
- GPU-accelerated rendering via gg → wgpu
- Granular widget invalidation — 11 widgets use
SetNeedsRedraw + InvalidateRectinstead of full-surface redraw - Accessibility from day one: 35+ ARIA roles, screen reader announcer
- Full i18n: CLDR plural rules, RTL detection
What's Coming Next
g3d — 3D Rendering Library
gogpu/g3d is the Three.js of Go. Scene graph, PBR materials (metallic-roughness), GLTF 2.0 loading, directional/point/spot lights, frustum culling, instance batching. Built on wgpu, not OpenGL.
scene := g3d.NewScene()
cube := g3d.NewMesh(
g3d.NewBoxGeometry(1, 1, 1),
g3d.NewStandardMaterial(),
)
scene.Add(cube)
renderer.Render(scene, camera)
compose — Declarative UI
gogpu/compose brings Jetpack Compose-style declarative UI to Go. Composable functions, automatic recomposition on state change, slot-based layout. Transport-pluggable architecture — same composable code can render to GPU window, headless image, or remote display.
systray — System Tray
gogpu/systray — cross-platform system tray icons. Win32 Notification Area, macOS Menu Bar Extra, Linux StatusNotifierItem/AppIndicator. Multiple trays, nested menus, notifications, dark mode icon switching. Pure Go, zero CGO.
Browser Target (WASM + WebGPU)
wgpu already has the build infrastructure for WASM (platform split shipped in v0.25.5). The plan: compile to WASM, use browser's native WebGPU API via syscall/js. Same app code, same shaders, runs in Chrome. Phase 0 complete, Phase 1 (navigator.gpu binding) is next.
Android Support
Under active discussion. The architecture is ready — wgpu's Vulkan backend works, naga generates SPIR-V, gogpu's PlatformManager/PlatformWindow abstraction was designed with mobile in mind. What remains: NativeActivity or GameActivity integration, touch input, lifecycle management (suspend/resume), and the build pipeline (gomobile or direct NDK).
ui — Major Overhaul Coming
The GUI toolkit is getting substantial improvements. The ADR-007 retained-mode compositor was just the beginning — next up is the full scene-graph pipeline where every widget records into display lists instead of immediate-mode drawing. More widgets, better performance, production-ready accessibility. The goal: a Go GUI toolkit that you'd actually choose over Electron for a desktop app.
Real Users, Real Software
This isn't a toy. People are building on GoGPU:
ironwail-go by @darkliquid — a Quake 1 engine port running on gogpu/wgpu Vulkan on Wayland+niri. The first 3D game on a Pure Go GPU stack. Demo video on the project README.
Born ML — our ML framework, migrating from Rust FFI (
go-webgpu/webgpu) to gogpu/wgpu. Single-binary deployment, DXIL backend for DX12 compute — things the Rust FFI path couldn't provide.L-System fractals by @rcarlier — 47 million points rendered via gg, running in WASM and CLI. "Amazing performance!"
@jdbann — contributing Metal backend fixes, testing on M4 Pro MacBook.
@SideFx — testing on Adreno mobile GPUs, helping us find driver-specific bugs.
The Architecture That Makes It Work
The secret is layered independence. Each layer does one thing, doesn't know about layers above it, and can be used standalone:
User Application
│
┌───┴────┬──────┬────────┐
gogpu/ui gogpu gg g3d ← Application layer
├────────┘ │ │ │
gpucontext ─┘ │ │ ← Shared interfaces
│ │ │
wgpu ────────────┘────┘ ← WebGPU implementation
│
naga ← Shader compiler
gogpu — The Windowing & Application Framework
gogpu/gogpu (~50K lines) is the foundation everything else builds on. Think of it as SDL or GLFW — but Pure Go, with a WebGPU renderer built in.
What it provides:
- Cross-platform windowing — Win32, Cocoa (AppKit), X11, Wayland. Pure Go, no CGO. All 4 platforms implemented from scratch using our goffi FFI library (syscall-level, no C compiler).
- GPU lifecycle — Instance → Adapter → Device → Queue → Surface. Dual backend: Pure Go (gogpu/wgpu) or Rust FFI (go-webgpu/webgpu). Backend selected at build time or runtime.
- Multi-window — shared GPU device, per-window swapchain, monotonic WindowID, focus-aware VSync (ADR-010).
- Event system — keyboard, mouse, touch (X11 XInput2), gestures, Unicode text input (IME) on all platforms.
- Multi-thread architecture — main thread for window events (keeps the OS happy), render thread for all GPU operations (keeps the GPU fed). Modal resize/drag on Windows handled via WM_TIMER callback.
-
HiDPI/Retina — per-monitor DPI, logical/physical coordinate split,
WM_DPICHANGEDon Windows. - Frameless windows — custom title bars with DWM shadow, hit-test regions (JetBrains Runtime pattern).
- Mouse grab / pointer lock — locked, confined, normal modes (SDL parity) on Win32, X11, Wayland.
- Software backend — always available, renders to GDI (Windows), XPutImage (X11), CALayer (macOS). No GPU required.
The App implements DeviceProvider — any library that accepts a gpucontext.DeviceProvider can use gogpu's GPU device without importing gogpu directly.
The Other Layers
naga (~195K lines) — shader compiler. WGSL in, SPIR-V/MSL/GLSL/HLSL/DXIL out. Used by wgpu at pipeline creation time.
wgpu (~145K lines) — Pure Go WebGPU implementation. 5 HAL backends (Vulkan, DX12, Metal, GLES, Software). Resource lifecycle, validation, state tracking. The layer between your draw calls and the GPU driver.
gg (~219K lines) — 2D graphics. Skia-class analytic AA rasterizer, Vello-derived tile rasterizer, GPU SDF accelerator, text rendering, SVG, alpha masks. Can work standalone (no window) or with gogpu's GPU device.
gpucontext (~3.6K lines) — the glue. Depends only on gputypes. Defines DeviceProvider, TextureView, CommandEncoder — interfaces that let all packages share GPU resources without circular imports. The database/sql of GPU programming.
Every layer can be used independently:
-
ggwithoutgogpu— 2D graphics without windowing -
wgpuwithoutgg— raw WebGPU for compute or custom rendering -
nagawithout anything — shader compilation as a library
What We Learned
1. Research before code. Every major feature starts with an Architecture Decision Record studying 5-8 enterprise implementations. The multi-window ADR studied Qt6, GTK4, SDL3, winit, GLFW, Fyne, and Gio before a line of code was written. This saved weeks of wrong turns.
2. CPU is core, GPU is accelerator. We analyzed 8 enterprise 2D engines (Skia, Cairo, Vello, Blend2D, tiny-skia, piet, Qt RHI, Pathfinder) and found that in zero of them is CPU rasterization a "backend." It's always the core. GPU accelerates specific operations. This insight shaped gg's entire architecture.
3. Zero CGO is a real competitive advantage. With the Rust FFI backend, every platform needed its own wgpu_native.dll/.so/.dylib — find it, download it, put it in the right place, hope the versions match. With Pure Go wgpu: go build and it works. No shared libraries to hunt for, no linker errors, no "works on my machine." Cross-compilation just works.
4. Enterprise references prevent enterprise bugs. When we tried to add DX12 texture barriers without studying how Rust wgpu handles them, we got TDR crashes at frame 575. When we studied the reference first, we found the root cause in 10 minutes.
5. Small teams win through focus. Our naga DXIL backend shipped before Rust naga's (open issue since 2020). Not because of more resources — because of fewer coordination costs and a clear research → design → implement pipeline. Architectural decisions compound: choosing WebGPU as the abstraction meant our Metal backend was ready when Apple deprecated OpenGL, and our shader IR was ready when DX12 needed DXIL.
Try It
# Install
go get github.com/gogpu/gogpu
# Run the particles demo (compute + render, zero CGO)
CGO_ENABLED=0 go run github.com/gogpu/gogpu/examples/particles@latest
# Run the multi-window demo
CGO_ENABLED=0 go run github.com/gogpu/gogpu/examples/multiwindow@latest
Requirements: Go 1.25+, a GPU with Vulkan/DX12/Metal/GLES support (or use Software backend for headless).
Links
| Resource | URL |
|---|---|
| GitHub Organization | github.com/gogpu |
| Main Repository | github.com/gogpu/gogpu |
| GUI Toolkit | github.com/gogpu/ui |
| 2D Graphics | github.com/gogpu/gg |
| 3D Library | github.com/gogpu/g3d |
| Shader Compiler | github.com/gogpu/naga |
| Previous: DXIL Article | dev.to/kolkov/dxil |
| Previous: 100K LOC Article | dev.to/kolkov/100k |
| Smart Coding Methodology | dev.to/kolkov/smart-coding |
The Core Is Stable — Now We Need You
The foundational libraries — wgpu, naga, gg, gogpu — have reached a level of stability where the focus is shifting. The WebGPU implementation handles Vulkan, DX12, Metal, GLES, and Software. The shader compiler generates 5 output formats. The 2D rasterizer passes thousands of tests. The windowing framework runs on 4 platforms with multi-window support.
Now the main effort goes into ui (the GUI toolkit), new ecosystem libraries (g3d, compose, systray), and the Browser backend (WASM + WebGPU — same app code running in Chrome). This is where things get exciting, and this is where we need feedback.
Try it. Break it. Tell us what's missing.
- Found a bug? Open an issue — we respond fast.
- Built something with GoGPU? Share it — we love showcasing community projects.
- Want to write about it? Articles and video reviews help the ecosystem grow more than anything else.
- Have ideas for the UI toolkit? Join the discussions.
Go deserves a real graphics ecosystem. We're building it — and it's ready for you to start building on.
Top comments (0)