DEV Community

Cover image for GoGPU: Unified 2D/3D Graphics Integration in Pure Go
Andrey Kolkov
Andrey Kolkov

Posted on

GoGPU: Unified 2D/3D Graphics Integration in Pure Go

Series: Building Go's GPU Ecosystem

  1. GoGPU: A Pure Go Graphics Library — Project announcement
  2. From Idea to 100K Lines in Two Weeks — The journey
  3. Pure Go 2D Graphics with GPU Acceleration — Introducing gg
  4. GPU Compute Shaders in Pure Go — Compute pipelines
  5. Go 1.26 Meets 2026 — Roadmap
  6. Enterprise 2D Graphics Library — gg architecture
  7. Cross-Package GPU Integration — gpucontext
  8. Unified 2D/3D Graphics Integration ← You are here

Introduction

Today we announce a major milestone for the GoGPU ecosystem: unified 2D/3D graphics integration through standardized interfaces. This release enables seamless rendering of 2D graphics (via gg) into GPU-accelerated windows (via gogpu) — all in Pure Go, without CGO.

This is the foundation for gogpu/ui, our upcoming enterprise-grade GUI toolkit.

The Problem We Solved

Modern applications need both 2D and 3D graphics:

  • UI elements (text, buttons, icons) require 2D rendering
  • Visualization (charts, graphs, CAD) requires GPU acceleration
  • Games and simulations require both

Traditionally, integrating these required:

  1. CGO bindings to native libraries (Cairo, Skia, Qt)
  2. Complex texture management between CPU and GPU
  3. Tight coupling between graphics and windowing code

We solved this with interface-based architecture — a pattern proven in enterprise systems.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    User Application                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────┐        ┌──────────────────────────┐   │
│   │ gg (2D Graphics)│        │  gogpu (GPU Framework)   │   │
│   │                 │        │                          │   │
│   │  - Canvas API   │        │  - WebGPU abstraction    │   │
│   │  - Text/Fonts   │        │  - Multi-backend         │   │
│   │  - Paths/Shapes │        │  - Window management     │   │
│   └────────┬────────┘        └────────────┬─────────────┘   │
│            │                              │                 │
│            └──────────┬───────────────────┘                 │
│                       │                                     │
│            ┌──────────▼──────────┐                          │
│            │  gpucontext         │                          │
│            │  (Shared Interfaces)│                          │
│            │                     │                          │
│            │  - TextureDrawer    │                          │
│            │  - TextureCreator   │                          │
│            │  - DeviceProvider   │                          │
│            │  - EventSource      │                          │
│            └─────────────────────┘                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The key insight: shared interfaces enable integration without coupling.

The TextureDrawer Interface

At the heart of our integration is gpucontext.TextureDrawer:

// gpucontext/texture.go

// TextureDrawer provides texture drawing capabilities for 2D rendering.
// This interface enables packages like ggcanvas to draw textures without
// depending directly on gogpu, following the Dependency Inversion Principle.
type TextureDrawer interface {
    // DrawTexture draws a texture at the specified position.
    DrawTexture(tex Texture, x, y float32) error

    // TextureCreator returns the texture creator associated with this drawer.
    TextureCreator() TextureCreator
}

// TextureCreator provides texture creation from raw pixel data.
type TextureCreator interface {
    // NewTextureFromRGBA creates a texture from RGBA pixel data.
    NewTextureFromRGBA(width, height int, data []byte) (Texture, error)
}
Enter fullscreen mode Exit fullscreen mode

This interface follows the Dependency Inversion Principle:

  • gg depends on abstractions (gpucontext.TextureDrawer)
  • gogpu implements abstractions (Context.AsTextureDrawer())
  • Neither depends on the other

Working Example

Here's a simplified example based on our codebase (see full version at examples/gg_integration/main.go):

package main

import (
    "log"
    "math"

    "github.com/gogpu/gg"
    "github.com/gogpu/gg/integration/ggcanvas"
    "github.com/gogpu/gogpu"
    "github.com/gogpu/gogpu/gmath"
)

func main() {
    // Create GPU-accelerated window
    app := gogpu.NewApp(gogpu.DefaultConfig().
        WithTitle("GoGPU + gg Integration via ggcanvas").
        WithSize(800, 600))

    var canvas *ggcanvas.Canvas

    app.OnDraw(func(dc *gogpu.Context) {
        w, h := dc.Width(), dc.Height()
        dc.ClearColor(gmath.Hex(0x1a1a2e))

        // Lazy initialization with GPU context
        if canvas == nil {
            var err error
            canvas, err = ggcanvas.New(app.GPUContextProvider(), w, h)
            if err != nil {
                log.Fatalf("Failed to create canvas: %v", err)
            }
        }

        // Draw 2D graphics using familiar gg API
        cc := canvas.Context()
        cc.SetRGB(1, 0.5, 0)
        cc.DrawCircle(400, 300, 100)
        cc.Fill()

        // Render to GPU window — one line!
        canvas.RenderTo(dc.AsTextureDrawer())
    })

    // Handle window resize
    app.EventSource().OnResize(func(w, h int) {
        if canvas != nil {
            canvas.Resize(w, h)
        }
    })

    app.Run()
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  1. ggcanvas.New(provider, w, h) — Creates a canvas with GPU context
  2. canvas.Context() — Returns standard *gg.Context for 2D drawing
  3. canvas.RenderTo(dc.AsTextureDrawer()) — Uploads to GPU and draws

Note: The full example includes animated HSV-colored circles, debug PNG export, backend logging, and comprehensive error handling.

The ggcanvas package handles all complexity:

  • CPU→GPU texture upload
  • Dirty tracking (only upload when changed)
  • Format conversion (RGBA→GPU texture)
  • Resource cleanup

Under the Hood

Data Flow

User draws via gg API
         │
         ▼
gg.Context accumulates draw commands
         │
         ▼
canvas.RenderTo(dc) called
         │
         ├─── cc.RenderToPixmap(pixmap)    [CPU rasterization]
         │
         ├─── texture.UpdateData(pixmap.Pix) [CPU→GPU upload]
         │
         └─── dc.DrawTexture(texture, 0, 0)  [GPU render]
         │
         ▼
Window surface (Vulkan/Metal/DX12)
Enter fullscreen mode Exit fullscreen mode

gogpu Implementation

gogpu.Context implements the interface via an adapter:

// context_texture.go

// AsTextureDrawer returns an adapter implementing gpucontext.TextureDrawer.
func (c *Context) AsTextureDrawer() gpucontext.TextureDrawer {
    return &contextTextureDrawer{
        ctx:     c,
        creator: &rendererTextureCreator{renderer: c.renderer},
    }
}
Enter fullscreen mode Exit fullscreen mode

This follows the Adapter Pattern — exposing existing functionality through a new interface without modifying the original type.

Platform Support

This integration works across all gogpu-supported platforms:

Platform Backend Status
Windows Vulkan, DX12 Production
Linux (X11) Vulkan Production
Linux (Wayland) Vulkan Production
macOS Metal Production

All platforms use Pure Go FFI — no CGO required.

Performance Characteristics

Operation Cost Notes
canvas.Context() O(1) Returns existing context
2D drawing CPU Rasterization in gg
RenderTo() O(pixels) CPU→GPU texture upload
GPU draw O(1) Single textured quad

For static or infrequently changing UI, the CPU→GPU upload happens only when content changes (dirty tracking).

Roadmap: gogpu/ui

This integration is the foundation for gogpu/ui, our upcoming GUI toolkit:

// Future gogpu/ui API (planned)

func main() {
    app := gogpu.NewApp(gogpu.DefaultConfig().
        WithTitle("gogpu/ui Demo").
        WithSize(1280, 720))

    // Declarative UI with reactive state
    count := signals.New(0)

    root := layout.VStack(
        widgets.Text("Counter Demo").FontSize(24),
        layout.HStack(
            widgets.Button("-").OnClick(func() {
                count.Set(count.Get() - 1)
            }),
            widgets.Text(signals.Computed(func() string {
                return fmt.Sprintf("Count: %d", count.Get())
            })),
            widgets.Button("+").OnClick(func() {
                count.Set(count.Get() + 1)
            }),
        ).Gap(8),
    ).Padding(24).Gap(16)

    ui := ui.NewRenderer(app.GPUContextProvider())
    ui.SetRoot(root)

    app.OnDraw(func(dc *gogpu.Context) {
        dc.ClearColor(theme.Background)
        ui.RenderTo(dc.AsTextureDrawer())
    })

    // Event forwarding
    app.EventSource().OnMouse(ui.HandleMouse)
    app.EventSource().OnKeyboard(ui.HandleKeyboard)
    app.EventSource().OnTouch(ui.HandleTouch)

    app.Run()
}
Enter fullscreen mode Exit fullscreen mode

Planned Features

Phase Version Features
Phase 1 v0.1.0 Core widgets, Flexbox layout, Events
Phase 2 v0.2.0 Material 3 theme, Animation
Phase 3 v0.3.0 Virtualization (100K+ items), A11y
Phase 4 v1.0.0 IDE docking, Multiple themes

Design Decisions

Based on our research of 7 Rust UI frameworks:

Decision Choice Rationale
Reactivity Fine-grained signals O(affected) updates only
Styling Tailwind-style builders Type-safe, IDE autocomplete
Layout Flexbox + incremental Industry standard
Accessibility AccessKit schema Cross-platform standard

Today's Release

Package Version Highlights
gpucontext v0.4.0 TextureDrawer, TouchEventSource
wgpu v0.12.0 BufferRowLength fix
naga v0.9.0 Shader compiler improvements
gg v0.22.1 Integration + LineJoinRound fix
gogpu v0.14.0 AsTextureDrawer() implementation

Getting Started

go get github.com/gogpu/gogpu@v0.14.0
go get github.com/gogpu/gg@v0.22.1
Enter fullscreen mode Exit fullscreen mode

Run the example:

git clone https://github.com/gogpu/gogpu
cd gogpu
go run examples/gg_integration/main.go
Enter fullscreen mode Exit fullscreen mode

Resources

Resource Link
GitHub Organization github.com/gogpu
RFC Discussion gogpu/ui RFC
gpucontext Article Cross-Package GPU Integration
gg Architecture Enterprise 2D Graphics Library
Project History From Idea to 100K Lines

We Need Your Help

Building an enterprise-grade graphics ecosystem is a massive undertaking. We're a small team, and we need the community's help:

Testing & Bug Reports

  • Platform testing — macOS, Linux (X11/Wayland), different GPUs
  • Edge cases — unusual window sizes, high DPI, multi-monitor setups
  • Performance issues — stuttering, memory leaks, high CPU usage

Validation & Feedback

  • API review — Does the API feel Go-idiomatic? What's confusing?
  • Architecture validation — Are we making the right design decisions?
  • Real-world usage — Try it in your projects and report pain points

What We're Especially Looking For

Area What We Need
Rendering bugs Incorrect colors, missing pixels, artifacts
Performance bottlenecks Profile and identify slow paths
API ergonomics Confusing names, missing methods, rough edges
Platform issues Windows/macOS/Linux-specific problems
Integration feedback How well does it fit your use case?

How to Contribute

  1. Star the repos — Helps visibility
  2. File issues — Even small bugs matter
  3. Join discussionsgithub.com/orgs/gogpu/discussions
  4. Try the examples — Report what breaks

Every bug report, performance profile, and piece of feedback helps us build the graphics library Go deserves.


Conclusion

With unified 2D/3D integration, the GoGPU ecosystem is ready for the next step: a production-grade GUI toolkit for Go.

Our approach:

  • Pure Go — No CGO, easy cross-compilation
  • Interface-based — Clean architecture, testable
  • Enterprise patterns — Proven in large-scale systems

We're building the GUI toolkit Go deserves. Join the discussion at github.com/orgs/gogpu/discussions/18.


The GoGPU Team

Follow the project:

Top comments (0)