README.md

May 11, 2026 · View on GitHub

gg

2D Graphics Library for Go
Pure Go | GPU Accelerated | Production Ready

CI codecov Go Reference Go Report Card License Latest Release Go Version

Part of the GoGPU ecosystem


Overview

gg is a 2D graphics library for Go designed to power IDEs, browsers, and graphics-intensive applications. Built with modern patterns inspired by vello and tiny-skia, it delivers production-grade rendering with zero CGO dependencies.


Six-tier GPU rendering: SDF shapes, convex polygons, stencil+cover paths, MSDF text, Vello compute, and glyph mask cache. Pure Go, Vulkan/DX12/GLES backends, zero CGO. (source)

Key Features

CategoryCapabilities
RenderingImmediate and retained mode, seven-tier GPU acceleration (SDF, Convex, Stencil+Cover, Textured Quad, MSDF Text, Compute, Glyph Mask), per-context GPU isolation (Skia GrContext pattern), scene GPU auto-select, Skia AAA pixel-perfect rasterizer, CPU fallback
ShapesRectangles, circles, ellipses, arcs, bezier curves, polygons, stars
TextTrueType fonts, MSDF + glyph mask dual-strategy rendering, TextMode auto-selection, DPI-aware HiDPI text, ClearType LCD auto-detection (Windows SPI + registry, macOS grayscale, Linux Xft/Wayland), CJK script-aware rendering (ADR-027: per-script hinting, exact-size rasterization, dual MSDF atlas 64/128px), font hinting (auto-hinter + CJK vertical-only), transform-aware CPU text (scale/rotate/shear), glyph outline caching, emoji support, bidirectional text, HarfBuzz shaping, scene text via TagText glyph references (shape-once, Skia drawTextBlob pattern), atlas zoom resilience (size buckets + frame-based compaction)
Compositing29 blend modes (Porter-Duff, Advanced, HSL), layer isolation, alpha masks, zero-readback compositor (non-MSAA blit fast path, damage-aware multi-rect sub-region updates, per-draw dynamic scissor ADR-028)
Images7 pixel formats, PNG/JPEG/WebP I/O, mipmaps, affine transforms
SVGFull SVG renderer (gg/svg): parse + render SVG XML with color override for theming, SVG path data parser (ParseSVGPath), transform-aware FillPath/StrokePath
Vector ExportRecording system with PDF and SVG backends
RasterizerSmart per-path algorithm selection (scanline, 4×4 tiles, 16×16 tiles, SDF, compute), text-aware area-based routing
PerformanceTile-based parallel rendering, LRU caching, four-level damage pipeline (ADR-021: object diff → tile dirty → GPU scissor → OS present), incremental Path.Bounds (Skia pattern), GOGPU_DEBUG_DAMAGE=1 overlay

Installation

go get github.com/gogpu/gg

Requirements: Go 1.25+


Quick Start

package main

import (
    "github.com/gogpu/gg"
    "github.com/gogpu/gg/text"
)

func main() {
    // Create drawing context
    dc := gg.NewContext(512, 512)
    defer dc.Close()

    dc.ClearWithColor(gg.White)

    // Draw shapes
    dc.SetHexColor("#3498db")
    dc.DrawCircle(256, 256, 100)
    dc.Fill()

    // Render text
    source, _ := text.NewFontSourceFromFile("arial.ttf")
    defer source.Close()

    dc.SetFont(source.Face(32))
    dc.SetColor(gg.Black)
    dc.DrawString("Hello, GoGPU!", 180, 260)

    dc.SavePNG("output.png")
}

Rendering

Software Rendering (Default)

The CPU rasterizer automatically selects the optimal algorithm per-path:

AlgorithmTilesBest For
AnalyticFiller (Skia AAA)Simple paths, small shapes (< 32px). Pixel-perfect with Skia Chrome/Android.
AnalyticFiller ConvexConvex shapes (rect, circle, triangle). 1.6x faster, kSnapDigit X snapping.
SparseStrips4×4Complex paths, CPU/SIMD workloads
TileCompute16×16Extreme complexity (10K+ segments)
dc := gg.NewContext(800, 600)
defer dc.Close()

// Auto-selection (default) — optimal algorithm per-path
dc.DrawCircle(400, 300, 100)
dc.Fill()

// Force specific algorithm for benchmarking
dc.SetRasterizerMode(gg.RasterizerSparseStrips)

GPU Acceleration (Optional)

gg supports optional GPU acceleration through the GPUAccelerator interface with a seven-tier rendering pipeline:

TierMethodBest For
1. SDFSigned Distance FieldCircles, ellipses, rectangles, rounded rects
2a. ConvexDirect vertex emissionConvex polygons, single draw call
2b. Stencil+CoverFan tessellation + stencil bufferArbitrary complex paths, EvenOdd/NonZero fill
3. Textured QuadGPU image samplingDrawImage, DrawGPUTexture (zero-readback compositing)
4. MSDF TextMulti-channel Signed Distance FieldDynamic/animated text, resolution-independent
5. Compute9-stage Vello compute pipelineFull scenes with many paths (GPU parallel rasterization)
6. Glyph MaskCPU-rasterized R8 alpha atlasStatic UI text ≤48px, pixel-perfect quality

Tiers 1–4, 6 use a render-pass pipeline; Tier 5 uses compute shaders dispatched via PipelineMode (Auto/RenderPass/Compute). Text auto-selection routes horizontal text ≤48px to Glyph Mask (Skia/Chrome pattern), else MSDF.

When no GPU is registered, rendering uses the high-quality CPU rasterizer (default).

import (
    "github.com/gogpu/gg"
    _ "github.com/gogpu/gg/gpu" // opt-in GPU acceleration
)

func main() {
    // GPU automatically used when available, falls back to CPU
    dc := gg.NewContext(800, 600)
    defer dc.Close()

    dc.DrawCircle(400, 300, 100)
    dc.Fill() // tries GPU first, falls back to CPU transparently
}

For zero-copy rendering directly to a GPU surface (e.g., in a gogpu window), use ggcanvas.Canvas.RenderDirect — see the gogpu integration example. The example uses event-driven rendering with AnimationToken for power-efficient VSync (0% CPU when idle).

Compositor examples:

ExampleDescription
zero_readback/GPU-direct rendering — SDF shapes + text rendered to swapchain in a single MSAA pass, zero CPU readback
blit_only/Non-MSAA compositor path — CPU pixmap uploaded via FlushPixmap, composited via DrawGPUTextureBase in a 1x render pass (93% bandwidth reduction, ADR-016)
zero_readback_manual/Manual zero-readback pipeline — FlushPixmap + EnsureGPUTexture + DrawGPUTextureBase + FlushGPUWithView step-by-step

Custom Pixmap

// Use existing pixmap
pm := gg.NewPixmap(800, 600)
dc := gg.NewContext(800, 600, gg.WithPixmap(pm))

Architecture

                        gg (Public API)

         ┌───────────────────┼───────────────────┐
         │                   │                   │
   Immediate Mode       Retained Mode        Resources
   (Context API)        (Scene Graph)     (Images, Fonts)
         │                   │                   │
         │              TagText (ADR-022)        │
         │          shape once → glyph refs      │
         └───────────────────┼───────────────────┘

              ┌──────────────┴──────────────┐
              │                             │
         CPU Raster                   GPUAccelerator
      (always available)            (opt-in via gg/gpu)
              │                             │
    internal/raster              ┌──────────┼──────────┐
                                 │          │          │
                           Render Pass   MSDF Text   Compute
                         (Tiers 1-4,6)  (Tier 4,6) (Tier 5)

Rendering Structure

ComponentLocationDescription
CPU Rasterinternal/raster/Skia AAA analytic anti-aliasing (pixel-perfect port of Chrome/Android rasterizer). General + convex fast path.
Tile Rasterizers`internal/gpu/(4×4),(4 \times 4),internal/gpu/tilecompute/$ (16 \times 16)\text{SparseStrips} + \text{TileCompute}, \text{both} \text{ported} \text{from} \text{Vello}
\text{GPU} \text{Accelerator}$internal/gpu`Seven-tier GPU pipeline (SDF, Convex, Stencil+Cover, Textured Quad, MSDF Text, Compute, Glyph Mask)
Scene Textscene/TagText glyph references (ADR-022): shape once at recording, resolve at render via DrawShapedGlyphs → Tier 6/4. Atlas zoom resilience (Skia size buckets).
GPU + Tilesgpu/Opt-in via import _ "github.com/gogpu/gg/gpu" (GPU + tile rasterizers)
Tiles Onlyraster/Opt-in via import _ "github.com/gogpu/gg/raster" (CPU-only tiles)
SoftwareRoot gg packageDefault CPU renderer with smart algorithm selection

Core APIs

Immediate Mode (Context)

Canvas-style drawing with transformation stack:

dc := gg.NewContext(800, 600)
defer dc.Close()

// Transforms
dc.Push()
dc.Translate(400, 300)
dc.Rotate(math.Pi / 4)
dc.DrawRectangle(-50, -50, 100, 100)
dc.SetRGB(0.2, 0.5, 0.8)
dc.Fill()
dc.Pop()

// Bezier paths
dc.MoveTo(100, 100)
dc.QuadraticTo(200, 50, 300, 100)
dc.CubicTo(350, 150, 350, 250, 300, 300)
dc.SetLineWidth(3)
dc.Stroke()

Fluent Path Builder

Type-safe path construction with method chaining:

path := gg.BuildPath().
    MoveTo(100, 100).
    LineTo(200, 100).
    QuadTo(250, 150, 200, 200).
    CubicTo(150, 250, 100, 250, 50, 200).
    Close().
    Circle(300, 150, 50).
    Star(400, 150, 40, 20, 5).
    Build()

dc.SetPath(path)
dc.Fill()

Retained Mode (Scene Graph)

GPU-optimized scene graph with compact encoding. Text uses TagText glyph references (ADR-022) — shaped once at recording time, resolved at render time with full hinting and atlas batching:

import (
    "github.com/gogpu/gg"
    "github.com/gogpu/gg/scene"
    "github.com/gogpu/gg/text"
)

s := scene.NewScene()

// Shapes — encoded as compact binary commands
s.Fill(scene.FillNonZero, scene.IdentityAffine(),
    scene.SolidBrush(gg.RGBA{R: 1, A: 0.8}),
    scene.NewCircleShape(150, 200, 100))

// Text — stored as glyph references (10 bytes/glyph, not vector paths)
source, _ := text.NewFontSourceFromFile("Roboto.ttf")
face := source.Face(16)
s.DrawText("Hello Scene", face, 50, 50, scene.SolidBrush(gg.White))

// Render through GPU scene renderer (Tier 6/4 text, SDF shapes)
gpuR := scene.NewGPUSceneRenderer(dc)
gpuR.RenderScene(s)

Text Rendering

Full Unicode support with font fallback and optional HarfBuzz-level shaping:

// Font composition
mainFont, _ := text.NewFontSourceFromFile("Roboto.ttf")
emojiFont, _ := text.NewFontSourceFromFile("NotoEmoji.ttf")
defer mainFont.Close()
defer emojiFont.Close()

multiFace, _ := text.NewMultiFace(
    mainFont.Face(24),
    text.NewFilteredFace(emojiFont.Face(24), text.RangeEmoji),
)

dc.SetFont(multiFace)
dc.DrawString("Hello World! Nice day!", 50, 100)

// Optional: enable HarfBuzz shaping for ligatures, kerning, complex scripts
shaper := text.NewGoTextShaper()
text.SetShaper(shaper)
defer text.SetShaper(nil)

// Text layout with wrapping
opts := text.LayoutOptions{
    MaxWidth:  400,
    WrapMode:  text.WrapWordChar,
    Alignment: text.AlignCenter,
}
layout := text.LayoutText("Long text...", face, opts)

Transform-Aware Text

Text rendering respects the full transformation matrix (scale, rotation, shear):

dc.SetFont(source.Face(24))
dc.SetRGB(0, 0, 0)

// Scaled text — rendered at device resolution (no pixelation)
dc.Push()
dc.Scale(2, 2)
dc.DrawString("2x Scale", 50, 50)
dc.Pop()

// Rotated text — glyph outlines converted to vector paths
dc.Push()
dc.Translate(200, 200)
dc.Rotate(math.Pi / 6) // 30 degrees
dc.DrawString("Rotated", 0, 0)
dc.Pop()

Text rendering strategy is selectable per-Context via SetTextMode():

ModeStrategyBest for
TextModeAutoAuto-select (default)General use
TextModeMSDFGPU MSDF atlasGames, animations, real-time scaling
TextModeVectorGlyph outlines as pathsUI labels, quality-critical text
TextModeBitmapCPU bitmap rasterizationPNG/PDF export, static text

The CPU text pipeline uses a three-tier strategy (modeled after Skia/Cairo/Vello): translation-only → bitmap, uniform scale ≤256px → bitmap at device size, everything else → glyph outlines as vector paths with cached outlines via GlyphCache.

Color Emoji

Full color emoji support with CBDT/CBLC (bitmap) and COLR/CPAL (vector) formats:

COLR Color Palette
175 colors from Segoe UI Emoji COLR/CPAL palette

// Extract color emoji from font
extractor, _ := emoji.NewCBDTExtractor(cbdtData, cblcData)
glyph, _ := extractor.GetGlyph(glyphID, ppem)
img, _ := png.Decode(bytes.NewReader(glyph.Data))

// Parse COLR/CPAL vector layers
parser, _ := emoji.NewCOLRParser(colrData, cpalData)
glyph, _ := parser.GetGlyph(glyphID, paletteIndex)
for _, layer := range glyph.Layers {
    // Render each layer with layer.Color
}

See examples/color_emoji/ for a complete example.

Layer Compositing

29 blend modes with isolated layers:

dc.PushLayer(gg.BlendOverlay, 0.7)

dc.SetRGB(1, 0, 0)
dc.DrawCircle(150, 200, 100)
dc.Fill()

dc.SetRGB(0, 0, 1)
dc.DrawCircle(250, 200, 100)
dc.Fill()

dc.PopLayer()

Alpha Masks

Per-shape masking — each draw individually masked:

// Create a circular mask
dc.DrawCircle(200, 200, 100)
mask := dc.AsMask() // capture path as mask (before Fill!)
dc.ClearPath()

// Apply mask: only the circle area is visible
dc.SetMask(mask)
dc.SetRGB(0, 0, 1)
dc.DrawRectangle(0, 0, 400, 400)
dc.Fill() // blue only inside the circle

Per-layer masking — mask an entire group of draws:

mask := gg.NewMask(400, 400)
// ... fill mask with desired shape ...

dc.PushMaskLayer(mask)
dc.DrawCircle(100, 100, 50)
dc.Fill()
dc.DrawRectangle(200, 200, 80, 80)
dc.Fill()
dc.PopLayer() // entire layer masked, then composited

Recording & Vector Export

Record drawing operations and export to PDF or SVG:

import (
    "github.com/gogpu/gg/recording"
    _ "github.com/gogpu/gg-pdf" // Register PDF backend
    _ "github.com/gogpu/gg-svg" // Register SVG backend
)

// Create recorder
rec := recording.NewRecorder(800, 600)

// Draw using familiar API
rec.SetColor(gg.Blue)
rec.DrawCircle(400, 300, 100)
rec.Fill()

// Finish recording and play back to a raster backend
r := rec.FinishRecording()
backend := raster.NewBackend()
r.Playback(backend)
backend.SaveToFile("output.png")

Why "Context" Instead of "Canvas"?

The drawing type is named Context following industry conventions:

LibraryDrawing Type
HTML5 CanvasCanvasRenderingContext2D
Cairocairo_t (context)
Apple CoreGraphicsCGContext
piet (Rust)RenderContext

In HTML5, Canvas is the element while Context performs drawing (canvas.getContext("2d")). The Context contains drawing state and provides the drawing API.

Convention: dc for drawing context, ctx for context.Context:

dc := gg.NewContext(512, 512) // dc = drawing context

Performance

OperationTimeNotes
sRGB to Linear0.16ns260x faster than math.Pow
LayerCache.Get90nsThread-safe LRU
DirtyRegion.Mark10.9nsLock-free atomic
MSDF lookup<10nsZero-allocation
Path iteration23nsSOA Iterate(), 0 allocs
FillRect77µs0 allocs (zero-alloc pipeline)
FillCircle r1002ms0 allocs (zero-alloc pipeline)
Gradient ColorAt33ns0 allocs (pre-sorted stops)

Debugging

Damage Overlay

Visualize which regions are repainted each frame:

GOGPU_DEBUG_DAMAGE=1 go run ./examples/gogpu_integration

Green flash-and-fade overlay shows damaged (repainted) regions. Useful for verifying that damage tracking works correctly and only dirty areas are repainted.

Environment Variables

VariableValuesDescription
GOGPU_GRAPHICS_APIvulkan, dx12, metal, gles, softwareForce specific GPU backend
GOGPU_RENDER_MODEauto, cpu, gpuForce CPU or GPU rasterizer (ADR-020)
GOGPU_DEBUG_DAMAGE1Show damage region overlay (flash-and-fade)

Ecosystem

gg is part of the GoGPU ecosystem.

ProjectDescription
gogpu/gogpuGPU framework with windowing and input
gogpu/wgpuPure Go WebGPU implementation
gogpu/nagaShader compiler (WGSL to SPIR-V, MSL, GLSL)
gogpu/gg2D graphics (this repo)
gogpu/gg-pdfPDF export backend for recording
gogpu/gg-svgSVG export backend for recording
gogpu/uiGUI toolkit (planned)

Documentation

Articles


Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

Priority areas:

  • API feedback and testing
  • Examples and documentation
  • Performance benchmarks
  • Cross-platform testing

License

MIT License — see LICENSE for details.


gg — 2D Graphics for Go