gogpu
GPU application framework with windowing, input, and rendering built on WebGPU — reduces 480+ lines of GPU code to ~20, zero CGO (GoGPU ecosystem: [gg](https://github.com/gogpu/gg), [ui](https://github.com/gogpu/ui), [wgpu](https://github.com/gogpu/wgpu), [naga](https://github.com/gogpu/naga))
About gogpu
This large README is shown as a plain-text preview. Read the full README at the source
<p align="center">
<img src="assets/logo.png" alt="GoGPU Logo" width="180" />
</p>
<h1 align="center">GoGPU</h1>
<p align="center">
<strong>Pure Go GPU Computing Ecosystem</strong><br>
GPU power, Go simplicity. Zero CGO.
</p>
<p align="center">
<a href="https://github.com/gogpu/gogpu/actions/workflows/ci.yml"><img src="https://github.com/gogpu/gogpu/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://codecov.io/gh/gogpu/gogpu"><img src="https://codecov.io/gh/gogpu/gogpu/branch/main/graph/badge.svg" alt="codecov"></a>
<a href="https://pkg.go.dev/github.com/gogpu/gogpu"><img src="https://pkg.go.dev/badge/github.com/gogpu/gogpu.svg" alt="Go Reference"></a>
<a href="https://goreportcard.com/report/github.com/gogpu/gogpu"><img src="https://goreportcard.com/badge/github.com/gogpu/gogpu" alt="Go Report Card"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"></a>
<a href="https://github.com/gogpu/gogpu/releases"><img src="https://img.shields.io/github/v/release/gogpu/gogpu" alt="Latest Release"></a>
<a href="https://github.com/gogpu/gogpu"><img src="https://img.shields.io/badge/Go-1.25+-00ADD8?logo=go" alt="Go Version"></a>
<a href="https://github.com/gogpu/gogpu/stargazers"><img src="https://img.shields.io/github/stars/gogpu/gogpu?style=flat&labelColor=555&color=yellow" alt="Stars"></a>
<a href="https://opencollective.com/gogpu"><img src="https://opencollective.com/gogpu/all/badge.svg?label=financial+contributors" alt="Financial Contributors on Open Collective"></a>
</p>
---
## Overview
**gogpu** is the application framework of the [GoGPU ecosystem](https://github.com/gogpu) — windowing, input, lifecycle management, and platform abstraction for GPU-accelerated applications. Part of a 1.1M+ LOC Pure Go ecosystem across 15 repositories.
Built on [gogpu/wgpu](https://github.com/gogpu/wgpu) — the unified Go WebGPU package with three backends: Pure Go (default), Rust FFI (`-tags rust`), and Browser WASM.
### Key Features
| Category | Capabilities |
|----------|--------------|
| **Backends** | Pure Go (default), Rust FFI (`-tags rust`), Browser WASM — via [gogpu/wgpu](https://github.com/gogpu/wgpu) |
| **Graphics API** | Runtime selection: Vulkan, DX12, Metal, GLES, Software |
| **Platforms** | Windows (Vulkan/DX12/GLES), Linux X11/Wayland (Vulkan/GLES), macOS (Metal), Browser/WASM (WebGPU) |
| **Rendering** | Event-driven three-state model (idle/animating/continuous), 0% CPU when idle (lazy acquire), zero-copy surface rendering, damage-aware presentation |
| **Input Models** | Three coexisting models: callbacks (`EventSource`), state polling (`Input()`, Ebiten-style), SDL-style event queue (`PollInputEvent()`) |
| **Graphics** | Windowing, input handling, multi-keyboard layout (X11 XKB + Wayland xkbcommon), AltGr/international text input (unified xkbcommon, ADR-029), key repeat on all platforms (Wayland client-side timer, ADR-033), texture loading, frameless windows, runtime window resize (`RequestSize`), mouse grab / pointer lock (Win32 + X11 + Wayland, SDL parity), OS drag-and-drop: incoming (all 4 desktop platforms) + outgoing drag source (`StartDrag`, all 5 platforms), GPU adapter power preference, native macOS window tabbing, native system menus (macOS + Windows), native file dialogs (macOS + Windows + Linux D-Bus/zenity/kdialog), native print contract and desktop backends (Windows/macOS/Linux portal) |
| **Scroll** | ScrollPhase + IsMomentum for macOS trackpad momentum detection (ADR-032), pixel/line/page delta modes |
| **Sound** | 12 UI sound types (UWP ElementSoundPlayer parity): Click, Invoke, Focus, Navigate, Show/Hide, feedback. winmm, NSSound, canberra |
| **Compute** | Full compute shader support |
| **Window Chrome** | Frameless windows with custom title bars, DWM shadow, hit-test regions |
| **HiDPI** | Per-monitor DPI, runtime DPI change (`ScaleChangedEvent` on all platforms), WM_DPICHANGED, logical/physical coordinate split, WithSize in logical DIP (ADR-030, ADR-059) |
| **Integration** | DeviceProvider, WindowProvider, PlatformProvider, WindowChrome, SurfaceView |
| **Logging** | Structured logging via `log/slog`, silent by default |
| **Build** | Zero CGO with Pure Go backend |
---
## Installation
```bash
go get github.com/gogpu/gogpu
```
**Requirements:**
- Go 1.25+
- `CGO_ENABLED=0` (Pure Go FFI requires CGO disabled)
**Zero dependencies — just works:**
```bash
CGO_ENABLED=0 go run .
```
> **Note:** On macOS and some Linux distros, CGO is enabled by default. Always set `CGO_ENABLED=0` when building GoGPU projects.
---
## Quick Start
```go
package main
import (
"github.com/gogpu/gogpu"
"github.com/gogpu/gogpu/gmath"
)
func main() {
app := gogpu.NewApp(gogpu.DefaultConfig().
WithTitle("Hello GoGPU").
WithSize(800, 600))
app.OnDraw(func(dc *gogpu.Context) {
dc.DrawTriangleColor(gmath.DarkGray)
})
app.Run()
}
```
**Result:** A window with a rendered triangle in approximately 20 lines of code, compared to 480+ lines of raw WebGPU.
---
## Backend Selection (ADR-038)
Backend selection is handled inside [gogpu/wgpu](https://github.com/gogpu/wgpu) via build tags. gogpu uses `wgpu.CreateInstance()` — the build tag determines which implementation runs.
```bash
# Pure Go (default, zero dependencies)
go build ./...
# Rust FFI (requires wgpu-native v29 shared library)
go build -tags rust ./...
# Download wgpu-native automatically:
go run github.com/go-webgpu/webgpu/cmd/setup@latest
```
| Backend | Build Tag | Use Case |
|---------|-----------|----------|
| **Pure Go** | (default) | Zero deps, cross-compile, air-gapped deploy |
| **Rust FFI** | `-tags rust` | Battle-tested GPU drivers, max compatibility |
| **Browser** | `GOOS=js GOARCH=wasm` | Web applications |
> **Note:** Rust backend requires [wgpu-native](https://github.com/gfx-rs/wgpu-native/releases/tag/v29.0.0.0). See [go-webgpu/webgpu](https://github.com/go-webgpu/webgpu) for setup.
### Graphics API Selection
Graphics API (Vulkan/DX12/Metal/GLES) is selected independently from the backend:
```go
// Force Vulkan on Windows (instead of auto-detected default)
app := gogpu.NewApp(gogpu.DefaultConfig().
WithGraphicsAPI(gogpu.GraphicsAPIVulkan))
// Force DirectX 12 on Windows
app := gogpu.NewApp(gogpu.DefaultConfig().
WithGraphicsAPI(gogpu.GraphicsAPIDX12))
// Force GLES (useful for testing or compatibility)
app := gogpu.NewApp(gogpu.DefaultConfig().
WithGraphicsAPI(gogpu.GraphicsAPIGLES))
// Software backend — no GPU required, always available
// Renders to screen (GDI, X11, Wayland SHM, macOS) or headless (CI/testing)
app := gogpu.NewApp(gogpu.DefaultConfig().
WithGraphicsAPI(gogpu.GraphicsAPISoftware))
```
| Graphics API | Platforms | Constant |
|--------------|-----------|----------|
| **Auto** | All (default) | `gogpu.GraphicsAPIAuto` |
| **Vulkan** | Windows, Linux | `gogpu.GraphicsAPIVulkan` |
| **DX12** | Windows | `gogpu.GraphicsAPIDX12` |
| **Metal** | macOS | `gogpu.GraphicsAPIMetal` |
| **GLES** | Windows, Linux | `gogpu.GraphicsAPIGLES` |
| **Software** | All (no GPU needed) | `gogpu.GraphicsAPISoftware` |
### Environment Variables
All settings can be overridden via environment variables (no code changes needed):
| Variable | Values | Default | Purpose |
|----------|--------|---------|---------|
| `GOGPU_GRAPHICS_API` | `vulkan`, `dx12`, `metal`, `gles`, `software` | auto | GPU backend selection |
| `GOGPU_POWER_PREFERENCE` | `low`, `high` | none | GPU power/performance trade-off |
| `GOGPU_RENDER_MODE` | `auto`, `cpu`, `gpu` | auto | 2D rendering path (ADR-020) |
| `GOGPU_DEBUG_DAMAGE` | `1` | off | Show damage region overlay (ADR-021) |
| `GOGPU_STATS` | `1` | off | Enable GPU upload/resource stats (#484) |
```bash
# Examples:
GOGPU_GRAPHICS_API=vulkan ./myapp # Force Vulkan
GOGPU_GRAPHICS_API=software ./myapp # Force software renderer
GOGPU_RENDER_MODE=cpu ./myapp # Force CPU rasterizer (benchmarking)
GOGPU_DEBUG_DAMAGE=overlay ./myapp # Show green overlay on dirty regions
```
`Config.With*()` methods in code take precedence over environment variables.
---
## Resource Management
GPU resources are automatically cleaned up on shutdown when registered with `TrackResource`:
```go
canvas, _ := ggcanvas.New(provider, 800, 600)
app.TrackResource(canvas) // auto-closed on shutdown, no OnClose needed
```
Resources are closed in LIFO (reverse) order after GPU idle, before device destruction. The shutdown sequence is: `WaitIdle → tracked resources → OnClose → Renderer.Destroy()`.
**ggcanvas auto-registration:** When created via a provider that implements `ResourceTracker` (like `App`), ggcanvas auto-registers — no `TrackResource` call needed.
**GC safety net:** Textures use `runtime.AddCleanup` as a fallback — if you forget `Destroy()`, the GC will eventually clean up GPU resources. This is a safety net, not a replacement for explicit cleanup.
---
## Texture Loading
```go
// Load from file (PNG, JPEG)
tex, err := renderer.LoadTexture("sprite.png")
defer tex.Destroy()
// Create from Go image
img := image.NewRGBA(image.Rect(0, 0, 128, 128))
tex, err := renderer.NewTextureFromImage(img)
// With custom filtering options
opts := gogpu.TextureOptions{
MagFilter: gputypes.FilterModeNearest, // Crisp pixels
AddressModeU: gputypes.AddressModeRepeat, // Tiling
}
tex, err := renderer.LoadTextureWithOptions("tile.png", opts)
```
---
## DeviceProvider Interface
GoGPU exposes GPU resources through the `DeviceProvider` interface for integration with external libraries:
```go
type DeviceProvider interface {
Device() hal.Device // HAL GPU device (type-safe Go interface)
Queue() hal.Queue // HAL command queue
SurfaceFormat() gputypes.TextureFormat
}
// Usage
provider := app.DeviceProvider()
device := provider.Device() // hal.Device — 30+ methods with error returns
queue := provider.Queue() // hal.Queue — Submit, WriteBuffer, ReadBuffer
```
### Cross-Package Integration (gpucontext)
For integration with external libraries like [gogpu/gg](https://github.com/gogpu/gg), use the standard [gpucontext](https://github.com/gogpu/gpucontext) interfaces:
```go
import "github.com/gogpu/gpucontext"
// Get gpucontext.DeviceProvider for external libraries
provider := app.GPUContextProvider()
device := provider.Device() // gpucontext.Device interface
queue := provider.Queue() // gpucontext.Queue interface
format := provider.SurfaceFormat() // gpucontext.TextureFormat
// Get gpucontext.EventSource for UI frameworks
events := app.EventSource()
events.OnKeyPress(func(key gpucontext.Key, mods gpucontext.Modifiers) {
// Handle keyboard input
})
events.OnMousePress(func(button gpucontext.MouseButton, x, y float64) {
// Handle mouse click
})
```
This enables enterprise-grade dependency injection between packages without circular imports.
### DeviceProvider (GPU Access)
For GPU compute and custom rendering, access the wgpu device directly:
```go
provider := app.DeviceProvider()
device := provider.Device() // *wgpu.Device — full WebGPU API
queue := device.Queue() // *wgpu.Queue — command submission
```
Used for compute shaders, custom render pipelines, and by [gogpu/gg](https://github.com/gogpu/gg) GPU accelerator.
### SurfaceView (Zero-Copy Rendering)
For direct GPU rendering without CPU readback:
```go
app.OnDraw(func(dc *gogpu.Context) {
view := dc.SurfaceView() // Current frame's GPU texture view
// Pass to ggcanvas.RenderDirect() for zero-copy compositing
})
```
This eliminates the GPU→CPU→GPU round-trip when integrating with gg/ggcanvas.
### Window & Platform Integration
`App` implements `gpucontext.WindowProvider` and `gpucontext.PlatformProvider` for UI frameworks:
```go
// Window geometry and DPI
w, h := app.Size() // logical points (DIP)
fw, fh := app.PhysicalSize() // physical pixels (framebuffer)
scale := app.ScaleFactor() // 1.0 = standard, 2.0 = Retina/HiDPI
app.RequestSize(1024, 768) // resize at runtime (all platforms, DPI-aware)
// Clipboard
text, _ := app.ClipboardRead()
app.ClipboardWrite("copied text")
// Cursor management
app.SetCursor(gpucontext.CursorPointer) // hand cursor
app.SetCursor(gpucontext.CursorText) // I-beam for text input
// Mouse grab / pointer lock (FPS games, 3D editors)
app.SetCursorMode(gpucontext.CursorModeLocked) // hide + capture relative deltas (SDL parity)
app.SetCursorMode(gpucontext.CursorModeConfined) // visible, confined to window
app.SetCursorMode(gpucontext.CursorModeNormal) // release
// System preferences
if app.DarkMode() { /* switch to dark theme */ }
if app.ReduceMotion() { /* disable animations */ }
if app.HighContrast() { /* increase contrast */ }
fontMul := app.FontScale() // user's font size preference
```
### Native printing contract
Applications provide complete PDF/document bytes and can submit them to the
desktop native print backends without coupling UI code to an operating system:
```go
job, err := app.Print(ctx, gogpu.NewPDFDocument("report.pdf", pdfBytes), gogpu.PrintOptions{})
if err != nil {
// Invalid input, unavailable native services, or an unsupported platform.
return err
}
if err := <-job.Done(); err != nil {
// context.Canceled means cancellation; other errors are native/spool errors.
return err
}
```
See [docs/PRINTING.md](docs/PRINTING.md) for parent-window, ownership, and
lifecycle semantics. The desktop backends accept PDF input through Windows
PrintDlgEx/GDI, macOS PDFKit/AppKit, and the Linux xdg-desktop-portal; browser
printing remains unsupported.
### macOS System Menu
Native menu bar with role-based items (About, Preferences, Quit, etc.):
```go
app := gogpu.NewApp(gogpu.DefaultConfig().WithAppName("My App"))
app.SetMenu(gogpu.NewMenu().
AddItem(gogpu.MenuItem{Title: "About My App", Role: gogpu.RoleAbout}).
AddItem(gogpu.MenuItem{Separator: true}).
AddItem(gogpu.MenuItem{Title: "Preferences…", Role: gogpu.RolePreferences}).
AddItem(gogpu.MenuItem{Separator: true}).
AddItem(gogpu.MenuItem{Title: "Quit", Role: gogpu.RoleQuit}),
)
if windowMenu := app.GetSystemMenu(gogpu.SystemMenuWindow); windowMenu != nil {
windowMenu.AddItem(gogpu.MenuItem{Title: "Minimize", Role: gogpu.RoleMinimize})
windowMenu.AddItem(gogpu.MenuItem{Title: "Close", Role: gogpu.RoleClose})
}
```
Menu can be set before or after `Run()`. On non-macOS platforms these calls are no-ops. See `examples/menu/` for a complete example.
### Ebiten-Style Input Polling
For game loops, use the polling-based Input API:
```go
import "github.com/gogpu/gogpu/input"
app.OnUpdate(func(dt float64) {
inp := app.Input()
// Keyboard
if inp.Keyboard().JustPressed(input.KeySpace) {
player.Jump()
}
if inp.Keyboard().Pressed(input.KeyLeft) {
player.MoveLeft(dt)
}
// Mouse
x, y := inp.Mouse().Position()
if inp.Mouse().JustPressed(input.MouseButtonLeft) {
player.Shoot(x, y)
}
})
```
All input methods are thread-safe and work with the frame-based update loop.
### SDL-Style Event Queue
For game developers who prefer explicit event processing (SDL/Pygame pattern):
```go
app.OnUpdate(func(dt float64) {
for ev, ok := app.PollInputEvent(); ok; ev, ok = app.PollInputEvent() {
switch e := ev.(type) {
case gpucontext.KeyEvent:
if e.Pressed && e.Key == gpucontext.KeyEscape {
app.Quit()
}
case gpucontext.PointerEvent:
if e.Type == gpucontext.PointerDown {
player.Shoot(e.X, e.Y)
}
case gpucontext.ScrollEvent:
camera.Zoom(e.DeltaY)
}
}
})
```
Three input models coexist — callbacks, polling, and event queue — all driven from the same internal dispatch. Use whichever fits your use case. Zero overhead for unused models.
### Multi-Window Input
Each window can have its own input handlers. `App.EventSource()` receives events from whichever window is focused:
```go
// Create secondary window
w2, _ := app.NewWindow(gogpu.DefaultConfig().WithTitle("Inspector"))
// Per-window input
w2.SetOnKeyPress(func(key gpucontext.Key, mods gpucontext.Modifiers) {
// fires only when w2 is focused
})
w2.SetOnPointer(func(ev gpucontext.PointerEvent) {
// pointer events for this window
})
// App-level: receives from ANY focused window
app.EventSource().OnKeyPress(func(key gpucontext.Key, mods gpucontext.Modifiers) {
if key == gpucontext.KeyN && mods.HasControl() {
// Ctrl+N works regardless of which window is focused
}
})
```
All input events flow through centralized dispatch with `WindowID` routing (winit/SDL3/Qt6 pattern).
### Window Lifecycle (ADR-026)
GPU Device is independent of any window. Closing a window destroys its surface but the device stays alive — remaining windows keep rendering. Same API on desktop, mobile, and web.
```go
// App lifecycle state: Idle → Running → Suspending → Suspended → Resuming
state := app.Lifecycle() // AppRunning on desktop
if state.IsActive() { ... } // true for Running/Suspending/Resuming
// Exit policy
app.SetQuitOnLastWindowClosed(false) // tray/headless: stay alive with zero windows
// Surface lifecycle (desktop: once at init; mobile: per-ANativeWindow)
app.OnSurfaceAvailable(func() { /* create GPU resources */ })
app.OnSurfaceDestroyed(func() { /* drop GPU surfaces NOW */ })
// App lifecycle (desktop: no-ops; mobile: Activity/UIApplication events)
app.OnResumed(func() { /* app visible */ })
app.OnSuspended(func() { /* app backgrounded, stop rendering */ })
app.OnMemoryWarning(func() { /* free caches */ })
```
See `examples/lifecycle/` for a visual demo: close the primary window, the secondary keeps rendering.
### Event-Driven Rendering
GoGPU uses a three-state rendering model for optimal power efficiency:
| State | Condition | CPU Usage | Latency |
|-------|-----------|-----------|---------|
| **Idle** | No activity | 0% (blocks on OS events) | <1ms wakeup |
| **Animating** | Active animation tokens | VSync (~60fps) | Smooth |
| **Continuous** | `WithContinuousRender(true)` | 100% (game loop) | Immediate |
```go
// Event-driven mode (default — 0% CPU when idle)
app := gogpu.NewApp(gogpu.DefaultConfig())
// Start animation — renders at VSync while token is alive
token := app.StartAnimation()
// ... animation runs at 60fps ...
token.Stop() // Loop returns to idle (0% CPU)
// Request single-frame redraw from any goroutine
app.RequestRedraw()
```
Multiple animation tokens can be active simultaneously. The loop renders continuously until all tokens are stopped.
### Resource Cleanup
Use `OnClose` to release GPU resources before the renderer is destroyed:
```go
app.OnClose(func() {
if canvas != nil {
_ = canvas.Close()
canvas = nil
}
})
if err := app.Run(); err != nil {
log.Fatal(err)
}
```
`OnClose` runs on the render thread before `Renderer.Destroy()`, ensuring textures, bind groups, and pipelines are released while the device is still alive.
---
## Compute Shaders
Full compute shader support via wgpu public API:
```go
device := app.DeviceProvider().Device()
// WGSL compute shader
shader, _ := device.CreateShaderModule(&wgpu.ShaderModuleDescriptor{
WGSL: `
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
output[id.x] = input[id.x] * 2.0;
}
`,
})
// Create storage buffers
inputBuf, _ := device.CreateBuffer(&wgpu.BufferDescriptor{
Size: dataSize,
Usage: wgpu.BufferUsageStorage | wgpu.BufferUsageCopyDst,
})
// Create pipeline and dispatchFrequently Asked Questions
What is gogpu?
gogpu is a Game Development library for the Go programming language. GPU application framework with windowing, input, and rendering built on WebGPU — reduces 480+ lines of GPU code to ~20, zero CGO (GoGPU ecosystem: [gg](https://github.com/gogpu/gg), [ui](https://github.com/gogpu/ui), [wgpu](https://github.com/gogpu/wgpu), [naga](https://github.com/gogpu/naga))
How do I install gogpu?
Install gogpu with the Go module system using `go get gogpu/gogpu`. Check the repository for the current installation instructions.
What category does gogpu belong to?
gogpu is listed under Game Development, specifically Game Development.