Replies: 6 comments 1 reply
|
I'm missing the knowledge and the experience do at more to this already almost completely plan. I like it and I'm observing the overall procedure. |
|
Any update from your side ? I defined what would be needed for my usecase at [KiGoCore] (https://github.com/AgentNemo00/kigo-core) and [KiGo] (https://github.com/AgentNemo00/kigo). |
|
@AgentNemo00 Sorry for the late reply — this fell through the cracks and that's not acceptable. We're growing the team now to make sure it doesn't happen again. What we've shipped for KiGo since your last messageLooking at our history — we've delivered a lot of what you need:
compose RFC statusHonest answer: But — do you still need multi-process? With offscreen rendering + multi-window + fullscreen already shipped, your KiGo modules could run as widgets in a single process. The multi-process approach (Option C from ui#75) adds complexity that might not be needed unless you require crash isolation between modules. What would help us prioritizeCould you update us on KiGo's current state?
If compose is still critical for your use case, we'll prioritize it. If in-process works — we can help you with the integration directly. Again, sorry for the wait. Your project is a great use case for our ecosystem and we want to support it properly. |
|
@kolkov thank you for the update. I'm already using the new updates expect the browser/wasm. I'm successfully having one process handling the screen and composing every image/widget received. Every module renders there one widget as wanted and sends them as images directly. The offscreen renderer is working fine. All requirements for building KiGo were fulfilled. I'm currently working on compressing the data to have a higher throughput. Compose would be a nice usecase for your guys but I already build a POC. No need from my side to push it currently. I'm currently implementing the basic modules. |
|
@AgentNemo00 Great news — compose v0.1.0 is released! 🎉 https://github.com/gogpu/compose/releases/tag/v0.1.0 This is the library version of the multi-process composition pattern you're already using in KiGo. Instead of rolling your own socket protocol, you can now use: // Module side (your KiGo modules)
client, _ := compose.Dial("/tmp/compose.sock",
compose.WithName("my-module"),
compose.WithFrameSize(400, 120),
)
defer client.Close()
// Pull-based: render only when compositor asks
client.OnFrameRequest(func() {
r := offscreen.NewRenderer(400, 120)
r.Render(myWidget)
client.PublishFrame(compose.Frame{
Pixels: r.Image().Pix,
Width: 400,
Height: 120,
})
})// Compositor side
srv, _ := compose.Listen("/tmp/compose.sock",
compose.WithCompression("lz4"), // 99.6% compression on GUI pixels
)
srv.OnFrame(func(f compose.Frame) {
// blit f.Pixels at assigned position
})What's included in v0.1.0:
What's next:
We'd love your feedbackYou're the first real user of this pattern — your input directly shapes the API before it freezes. If you get a chance to try it with KiGo, we're especially interested in:
Take your time testing. We'll iterate based on your feedback before freezing the API. |
|
Thank you for the great work and the food for thoughts. It looks great.
I will probably take the hint with the LZ4 compression. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
RFC: compose — multi-process composition library for Go
TL;DR
We are creating a new standalone library,
gogpu/compose, dedicated to multi-process UI composition. Each module is a separate OS binary that renders into an offscreen buffer (usinggogpu/gg,gogpu/ui, or any other rendering library) and ships pixels via Unix domain socket or POSIX shared memory to a compositor process. The compositor blits the frames into one display, with process isolation, hot-pluggable modules, and zero CGO.The library is in design phase. The first concrete user is a Go rewrite of MagicMirror². Public repo:
github.com/gogpu/compose.Why now
Three signals converged in early April 2026:
The first version is opinionated, focused, and intentionally narrow: get one concrete user (Magic Mirror Go) to ship something real with it, then expand from there.
Architecture overview
graph TD DISP["<b>Display</b><br/>one physical screen"] DISP --> COMP COMP["<b>Compositor process</b><br/>gogpu window owns the surface<br/>accepts module connections<br/>blits incoming frames<br/>handles hot-plug and lifecycles"] COMP --> M1 COMP --> M2 COMP --> M3 M1["<b>Clock module</b><br/>own process · own GPU<br/>gg primitives · 1 Hz<br/>Unix socket"] M2["<b>Weather module</b><br/>own process · own GPU<br/>gg primitives · 0.1 Hz<br/>Unix socket"] M3["<b>Notification module</b><br/>own process · own GPU<br/>ui widgets · 60 Hz · animated<br/>shared memory ring buffer"] style DISP fill:#3d2d5a,stroke:#9a4,color:#fff style COMP fill:#2d5a3d,stroke:#4a9,color:#fff style M1 fill:#1a3a5c,stroke:#4a9,color:#fff style M2 fill:#1a3a5c,stroke:#4a9,color:#fff style M3 fill:#1a3a5c,stroke:#4a9,color:#fffEach module owns its own process, its own GPU
Device(if it uses GPU at all), its own crash domain, and its own release lifecycle. The compositor is the only process that touches the actual display surface. Modules communicate exclusively through pixels over IPC — no shared memory addresses, no shared GPU resources, no Go pointers crossing process boundaries.Properties
AF_UNIXnatively since the 1803 release in April 2018, sonet.Listen("unix", ...)from Go's standard library Just Works on Windows too. Future: Redox OS when its Go toolchain matures.mmapon Unix-likes andCreateFileMapping/MapViewOfFileon Windows, behind a unified Go interface with build tags. No Linux-specific kernel APIs (io_uring,eventfd, Linux-specific shm segments) — the design ports cleanly to FreeBSD and (eventually) Redox.Why a separate library?
composedeliberately does not live insidegogpu/uiorgogpu/gogpu. Five reasons:gg,ui, any third-party Go drawing library, or no Go at all. Anchoring composition inside a UI library would imply that the UI library is a hard prerequisite. It is not.gogpuis the per-process app framework. Multi-process composition is a different problem area with different lifecycle, trust, and protocol concerns.mmap, ring buffers, POSIX-specific transport code — users of the UI framework who are building a normal desktop application have no reason to pay the dependency cost of multi-process primitives they will never invoke.How is this different from multi-window?
There are two "multi-something" architectural concepts in our ecosystem, and they are easy to confuse. Both are valid for their own use cases, and both are independently on the roadmap.
DevicesNo row matches. They are mathematically opposite: multi-window is
1 process → N windows, compose isN processes → 1 display.The two stack cleanly. A
composecompositor process can use multi-window internally if it wants to span multiple physical monitors. Acomposemodule can use multi-window internally if it wants debug sub-windows. They compose, but they are not interchangeable: trying to make multi-window cover the multi-process case would force every gogpu user to pay the cost of IPC even for a simple dialog box; trying to make compose cover the single-process case would force an IDE to spawn a process per panel and pay milliseconds of latency for what should be a function call.For the full multi-window architectural draft, see #167.
How is this different from JavaScript Module Federation?
For readers coming from a web background, the obvious first question is "isn't this just Module Federation but for Go?" The answer is no, they live at very different levels of abstraction — though they solve a superficially similar problem ("modular architecture with independently-built modules").
sharescope deduplicates React, Angular core, etc. between host and remotesDevice, allocatorThe deepest difference is integration granularity. Module Federation is tight coupling at the type and runtime level — the host imports a remote
<RemoteCounter />component and renders it inside its own React tree as if it were local code. compose is loose coupling through a wire protocol — the compositor receives a bitmap and blits it without knowing what produced the pixels, what types live inside the module, or even what language the module was written in.A more accurate analogy from the browser world is not Module Federation but Chromium's Site Isolation with cross-origin iframes: each cross-origin iframe runs in its own OS process, the parent receives only the final pixels of the iframe surface, cross-frame communication happens through
postMessage(which is IPC under the hood), and a renderer crash in one iframe does not take down the parent. compose is essentially this pattern generalized into a library for native Go processes instead of web pages.The native-systems analogies are even more direct: Wayland compositors, X11 + Xcomposite, Android SurfaceFlinger, macOS WindowServer, and Chromium's viz service all implement variations of "many processes feed pixels into one composited display". compose is bringing this established systems pattern to the Go ecosystem as a portable, POSIX-only, zero-CGO library.
Update (2026-04-13): transport-pluggable control plane
The original draft of this RFC proposed a single transport — Unix domain sockets — for both the control plane (module registration, lifecycle events, "frame ready" notifications, commands) and the data plane (the actual pixel bitmaps). Feedback in gogpu/ui#75 from the first user (@AgentNemo00, building KiGo — a Go rewrite of MagicMirror²) pointed out that this is too prescriptive: real users already have message buses in their stacks, and forcing them to bridge into a Unix-socket-only model is friction. AgentNemo00's KiGo, for example, is built around NATS for control-plane messaging.
After thinking about this, the original design was wrong, and the better model splits the two planes:
composedefines the wire protocol and the message types, not how messages travel between processes. Anything that satisfies a smallTransportinterface (publish, subscribe, request/reply) can be plugged in.This split matches the real-world architectures of every multi-process compositor we have studied: Wayland uses its own protocol for control plus DMA-BUF / shared memory for buffers; SurfaceFlinger uses Binder for control plus Gralloc-allocated buffers for surfaces; Chromium's viz service uses Mojo IPC for control plus shared memory for compositor frames. The control/data split is universal once you look closely.
composebecomes:Transportinterface — small abstraction (publish, subscribe, request/reply) that any message bus can satisfy. Reference implementation incomposewill be a Unix-domain-socket transport, but users are free to bring NATS, gRPC, Redis, etc.mmapon Unix-likes andCreateFileMappingon Windows, behind a unified Go interface. Standard, not pluggable.ADR-004 in the
composerepo will capture this decision in detail. The RFC body above will be updated to remove the "Unix socket only" framing in the transport section once the ADR is finalized. Credit for the architectural insight goes to AgentNemo00 — the design change is directly driven by his feedback in ui#75, andcomposeis meaningfully better for it.Use cases
Roadmap
Each phase is independently releasable. The reference example is the proving ground; library extraction happens only after at least two real use cases agree on the API shape (the example-first extraction strategy).
Status
github.com/gogpu/compose— live, design phase scaffolding only (README, LICENSE,doc.go,go.mod). ADRs and kanban tasks live in the repo's gitignoreddocs/dev/during design phase.The first user is the Go rewrite of MagicMirror² targeting Raspberry Pi and eventually Redox OS. The detailed practical thread (offscreen rendering with
gg, render-loop pattern, IPC bandwidth budgets, calibration questions) lives there.What we want from this discussion
This is a pre-implementation RFC. We deliberately want feedback before any code lands so the API is shaped by real use cases rather than speculation:
gobframing).References
Thank you for reading this far. Multi-process composition in Go is one of the architectural commitments we want to get right by talking to real users before writing code, not after. If you have a use case, a concern, a war story from another ecosystem, or a "this will never work because…" — please share it in this thread.
All reactions