Awesome Go

gogpu

CategoryGame Development
SubcategoryGame Development
Stars413

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 dispatch

Frequently 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.

← Back to Game Development