detached 6bfd2d4

docs(readme): restore original Go rationale section (#141)

commit
6bfd2d43683c90547b7374e8781a2e2fee8ee839
author
bnema · · 2026-10-05 17:43 UTC
committer
GitHub · · 2026-10-05 17:43 UTC
parent
06e1112
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
1 file · +24 -16
M README.md +24 -16
@@ -24,31 +24,39 @@ About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen
2424
2525 ## OK, but why Go?
2626
27-Most Wayland compositors are written in C, C++ or Rust. The usual objection to Go is the garbage collector. Fair, so here is how NeferWL deals with it.
27+Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). The usual objection to Go is the garbage collector. It is a fair one, so here is how NeferWL deals with it, and why Go turns out to fit a compositor well.
2828
29-A compositor is a pile of streams that never stop: input, client commits, one vblank per monitor, GPU fences, captures, hotplug. A slow client must not delay a flip, and rendering must not delay input. Go gives me cheap goroutines and channels to keep them apart. NeferWL follows one rule throughout: **each piece of state has one owner goroutine, and others talk to it over channels.**
29+### A compositor is a concurrency problem
3030
31-Input, core, the Wayland server and each output run independently. No mutex on window state, no lock ordering to get wrong, and the race detector checks every test. With CAP_SYS_NICE, input and output threads request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
31+A compositor sits in the middle of many independent streams that never stop: keyboard and pointer events, client requests and buffer commits, one vblank and page-flip cycle per monitor, frame fences from the GPU, presentation feedback, screen captures, config reloads, hotplug, idle and lock state. None of them should wait on another. A slow client must not delay a flip, and rendering must not delay input.
3232
33-### Keeping GC away from frames
33+Go has this built in. Goroutines are cheap, channels carry messages between them, and the runtime spreads them over every CPU core. NeferWL follows one idiom everywhere: **each piece of state has one owner goroutine, and others talk to it over channels.**
3434
35-- **Reuse memory on hot paths.** `make check` includes allocation guards for frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates.
36-- **Keep buffers off the Go heap.** Client buffers live in shared or GPU memory. The live Go heap is only a few megabytes: playing 4K HDR video with 65 tiled subsurfaces, GC takes under 9 % of a process that itself uses 13 % of a core.
37-- **Build without cgo.** libwayland, Vulkan and libinput load at runtime through [purego](https://github.com/ebitengine/purego).
38-- **Render only changes.** Damage tracking limits redraws, opaque surfaces hide work behind them, and idle outputs do nothing.
39-- **Tune on real hardware.** Under VRR, cursor moves ride on the game's next frame, and game frames keep a short gap after each flip so the panel does not drop to its slowest rate.
35+- **Input** runs on its own thread, reads libinput and sends events to core.
36+- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
37+- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
38+- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
39+- **Capture, config, idle, sessions and leases** each run in their own goroutine.
4040
41-A full build takes seconds, and testing, race detection, profiling and formatting come with Go. That makes it a pleasure to work on. For measurements and the rendering pipeline, see [Performance](docs/performance.md).
41+About 35 channels connect them, all created in one place, so the full data flow is easy to read. There is no mutex on window state, no lock ordering to get wrong, and the race detector checks every test. When CAP_SYS_NICE is granted, the input and output goroutines lock their OS thread and request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
4242
43-### Reusable libraries
43+### Keeping the garbage collector out of the way
4444
45-I like bringing more tools to the Go ecosystem, so NeferWL grew its own libraries along the way. Other Go projects can use them:
45+- **The hot path does not allocate.** Frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates reuse memory owned by the goroutine that handles them. `make check` runs allocation guards that fail the build when one of these paths starts allocating.
46+- **The GC has almost nothing to collect.** Client buffers live in shared and GPU memory, not on the Go heap, so the live heap is a few megabytes. Playing a 4K HDR video with 65 tiled subsurfaces committing every frame, GC takes under 9 % of a process that itself takes 13 % of a core. See [Performance](docs/performance.md).
47+- **No cgo.** NeferWL builds with `CGO_ENABLED=0`. libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego) with fixed-arity calls: no reflection, no allocation at the boundary.
48+- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
49+- **Tuned on real hardware.** Under VRR, cursor moves ride on the game's next frame instead of forcing their own refresh, and game frames keep a short gap after each flip so the panel does not fall back to its slowest rate.
50+
51+### A pleasure to work on
52+
53+A full build takes seconds, and tests, race detection, profiling and formatting come with the language. I also like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, and other Go projects can use them:
4654
4755 - [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.
48-- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan bindings generated from `vk.xml`, without cgo.
49-- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): protocol bindings generated from upstream XML.
50-- [wlturbo](https://github.com/bnema/wlturbo): a Wayland client library.
51-- [neferclient](https://github.com/bnema/neferclient): a client toolkit for connections, outputs, surface roles, seats and dmabuf presentation.
56+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`.
57+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.
58+- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.
59+- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.
5260
5361 ## Features
5462
@@ -24,31 +24,39 @@ About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen
24 24
25 ## OK, but why Go?25 ## OK, but why Go?
26 26
27-Most Wayland compositors are written in C, C++ or Rust. The usual objection to Go is the garbage collector. Fair, so here is how NeferWL deals with it.27+Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). The usual objection to Go is the garbage collector. It is a fair one, so here is how NeferWL deals with it, and why Go turns out to fit a compositor well.
28 28
29-A compositor is a pile of streams that never stop: input, client commits, one vblank per monitor, GPU fences, captures, hotplug. A slow client must not delay a flip, and rendering must not delay input. Go gives me cheap goroutines and channels to keep them apart. NeferWL follows one rule throughout: **each piece of state has one owner goroutine, and others talk to it over channels.**29+### A compositor is a concurrency problem
30 30
31-Input, core, the Wayland server and each output run independently. No mutex on window state, no lock ordering to get wrong, and the race detector checks every test. With CAP_SYS_NICE, input and output threads request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.31+A compositor sits in the middle of many independent streams that never stop: keyboard and pointer events, client requests and buffer commits, one vblank and page-flip cycle per monitor, frame fences from the GPU, presentation feedback, screen captures, config reloads, hotplug, idle and lock state. None of them should wait on another. A slow client must not delay a flip, and rendering must not delay input.
32 32
33-### Keeping GC away from frames33+Go has this built in. Goroutines are cheap, channels carry messages between them, and the runtime spreads them over every CPU core. NeferWL follows one idiom everywhere: **each piece of state has one owner goroutine, and others talk to it over channels.**
34 34
35-- **Reuse memory on hot paths.** `make check` includes allocation guards for frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates.35+- **Input** runs on its own thread, reads libinput and sends events to core.
36-- **Keep buffers off the Go heap.** Client buffers live in shared or GPU memory. The live Go heap is only a few megabytes: playing 4K HDR video with 65 tiled subsurfaces, GC takes under 9 % of a process that itself uses 13 % of a core.36+- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
37-- **Build without cgo.** libwayland, Vulkan and libinput load at runtime through [purego](https://github.com/ebitengine/purego).37+- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
38-- **Render only changes.** Damage tracking limits redraws, opaque surfaces hide work behind them, and idle outputs do nothing.38+- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
39-- **Tune on real hardware.** Under VRR, cursor moves ride on the game's next frame, and game frames keep a short gap after each flip so the panel does not drop to its slowest rate.39+- **Capture, config, idle, sessions and leases** each run in their own goroutine.
40 40
41-A full build takes seconds, and testing, race detection, profiling and formatting come with Go. That makes it a pleasure to work on. For measurements and the rendering pipeline, see [Performance](docs/performance.md).41+About 35 channels connect them, all created in one place, so the full data flow is easy to read. There is no mutex on window state, no lock ordering to get wrong, and the race detector checks every test. When CAP_SYS_NICE is granted, the input and output goroutines lock their OS thread and request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
42 42
43-### Reusable libraries43+### Keeping the garbage collector out of the way
44 44
45-I like bringing more tools to the Go ecosystem, so NeferWL grew its own libraries along the way. Other Go projects can use them:45+- **The hot path does not allocate.** Frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates reuse memory owned by the goroutine that handles them. `make check` runs allocation guards that fail the build when one of these paths starts allocating.
46+- **The GC has almost nothing to collect.** Client buffers live in shared and GPU memory, not on the Go heap, so the live heap is a few megabytes. Playing a 4K HDR video with 65 tiled subsurfaces committing every frame, GC takes under 9 % of a process that itself takes 13 % of a core. See [Performance](docs/performance.md).
47+- **No cgo.** NeferWL builds with `CGO_ENABLED=0`. libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego) with fixed-arity calls: no reflection, no allocation at the boundary.
48+- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
49+- **Tuned on real hardware.** Under VRR, cursor moves ride on the game's next frame instead of forcing their own refresh, and game frames keep a short gap after each flip so the panel does not fall back to its slowest rate.
50+
51+### A pleasure to work on
52+
53+A full build takes seconds, and tests, race detection, profiling and formatting come with the language. I also like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, and other Go projects can use them:
46 54
47 - [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.55 - [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.
48-- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan bindings generated from `vk.xml`, without cgo.56+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`.
49-- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): protocol bindings generated from upstream XML.57+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.
50-- [wlturbo](https://github.com/bnema/wlturbo): a Wayland client library.58+- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.
51-- [neferclient](https://github.com/bnema/neferclient): a client toolkit for connections, outputs, surface roles, seats and dmabuf presentation.59+- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.
52 60
53 ## Features61 ## Features
54 62

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close