gogpu/gg

Enterprise-grade 2D graphics library for Go. Pure Go, zero CGO. Part of GoGPU ecosystem.

Go

167

529 commits

updated Sep 10, 2026

See the code

README

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.

Seven-Tier GPU Rendering: SDF shapes, convex polygons, stencil+cover paths, MSDF text, Vello compute pipeline, glyph mask cache
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, pixel-perfect mode (SetAntiAlias(false) — dedicated NoAAFiller, Skia/Cairo/tiny-skia pattern), CPU fallback
ShapesRectangles, circles, ellipses, arcs, bezier curves, polygons, stars
TextPure Go font stack (own cmap/hmtx/GSUB/GPOS/gvar/avar/CFF1 parsers, TrueType bytecode interpreter — skrifa golden parity 624/624 diff=0), variable font support (WithVariations — weight/width/slant axes, own gvar/HVAR interpolation), MSDF + glyph mask dual-strategy rendering, TextMode auto-selection, text stroke/outline (StrokeString + TextPath, Skia/Cairo/HTML5 pattern), aliased text (TextModeAliased, Skia kAlias binary masks — GPU + CPU), per-glyph rendering (fractional advances, Skia linearMetrics pattern), OpenType font features (WithFeatures(TabularNums, NoLigatures) — own GSUB/GPOS shaper, 5-10x faster than HarfBuzz), 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 (enterprise auto-hinter + TrueType bytecode interpreter), transform-aware CPU text (scale/rotate/shear), glyph outline caching, emoji support, bidirectional text, 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, HiDPI-aware damage tracking (logical→physical scaling for OS compositor), 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, retained scene lowering (RenderToScene — resolution-independent vector icons via scene graph), thin stroke hinting (pixel-grid snapping for crisp small icons), 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), text batch coalescing (ADR-031: same-style DrawString calls merge into 1 GPU draw call, Skia TextBlob pattern), zero-copy buffer reuse (NewPixmapFromBuffer + ImageView for hot rendering loops), incremental Path.Bounds (Skia pattern), GOGPU_DEBUG_DAMAGE=overlay 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")
}

Common Patterns

// Fill AND stroke the same shape (use FillPreserve to keep the path):
dc.SetHexColor("#3498db")
dc.DrawRoundedRectangle(50, 50, 200, 100, 10)
dc.FillPreserve()               // fill WITHOUT clearing the path
dc.SetHexColor("#2c3e50")
dc.SetLineWidth(3)
dc.Stroke()                     // stroke the same path

// Clear canvas: Clear() fills transparent, ClearWithColor() fills with color:
dc.ClearWithColor(gg.White)     // white background (recommended)
dc.Clear()                      // transparent background (for compositing)

// Load a font (one-liner):
dc.LoadFontFace("arial.ttf", 24)
dc.DrawString("Hello!", 50, 50)

// Scale/rotate around a point:
dc.ScaleAbout(2, 2, 100, 100)   // 2x zoom centered at (100,100)
dc.RotateAbout(0.5, 200, 200)   // rotate 0.5 rad around (200,200)

Rendering

Software Rendering (Default)

The CPU rasterizer automatically selects the optimal algorithm per-path. Curve edges use forward differencing with deviation-based subdivision by default (ADR-063) -- native O(1)/step curve stepping with De Casteljau split for large curves (deviation > 0.1px). Value-type QuadraticEdge/CubicEdge with zero per-edge heap allocations.

AlgorithmTilesBest For
AnalyticFiller (Skia AAA)—Simple paths, small shapes (< 32px). Forward-diff curve edges (Skia updateQuadratic/updateCubic). Pixel-perfect with Chrome/Android.
AnalyticFiller Convex—Convex 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)

Pixel-Perfect Mode (No Anti-Aliasing)

Disable anti-aliasing for crisp, aliased edges — useful for pixel art, retro graphics, technical drawings, and sharp grid lines:

dc.SetAntiAlias(false)          // binary coverage: inside=opaque, outside=transparent
dc.DrawRectangle(10, 10, 100, 50)
dc.Fill()                        // no gray edge pixels
dc.SetAntiAlias(true)           // back to smooth AA

Uses a dedicated integer scanline rasterizer (Skia/tiny-skia pattern) — ~2-3× faster than analytic AA. Works on both CPU and GPU (all backends). Text AA is independent (controlled via SetTextMode).

Text Stroke & Outline

Stroke text outlines for outlined/bordered text effects (Skia/Cairo/HTML5 pattern):

dc.SetLineWidth(3)
dc.SetRGB(0, 0, 0)
dc.StrokeString("Hello", x, y)  // black outline
dc.SetRGB(1, 1, 1)
dc.DrawString("Hello", x, y)    // white fill on top

// Or get text as a Path for custom operations:
path := dc.TextPath("Hello", x, y)

Aliased Text

Pixel-perfect text with binary coverage (Skia SkFont::Edging::kAlias). Works on both GPU and CPU — no GPU required:

dc.SetTextMode(gg.TextModeAliased)  // no gray edge pixels on text
dc.DrawString("Pixel Perfect", x, y)
dc.SavePNG("aliased.png")           // works standalone, no import _ "gg/gpu" needed

Geometry AA (SetAntiAlias) and text AA (SetTextMode) are independent — matching Skia and Cairo separation.

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 ≤64px, 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 ≤64px 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. Forward-diff curve edges with deviation-based subdivision (ADR-063), value-type QuadraticEdge/CubicEdge (zero per-edge heap allocs).
Tile Rasterizersinternal/gpu/ (4×4), internal/gpu/tilecompute/ (16×16)SparseStrips + TileCompute, both ported from Vello
GPU Acceleratorinternal/gpuSeven-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 built-in GSUB/GPOS 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)

// Built-in GSUB/GPOS shaping (ligatures, kerning) is enabled by default.
// No additional setup needed — OwnShaper handles liga, kern features automatically.

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

Blend Modes

29 W3C blend modes for direct Fill/Stroke operations:

// Per-operation blending (Skia/Cairo/Qt pattern):
dc.SetRGB(1, 0, 0) // red background
dc.DrawRectangle(0, 0, 200, 200)
dc.Fill()

dc.SetBlendMode(gg.BlendMultiply)
dc.SetRGB(0, 0, 1) // blue foreground
dc.DrawCircle(100, 100, 80)
dc.Fill() // uses Multiply blending

dc.SetBlendMode(gg.BlendNormal) // reset to default

For isolated compositing with opacity, use layers:

dc.PushLayer(gg.BlendOverlay, 0.7)
// ... draw content ...
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
FillRect 100×100520µs0 allocs (zero-alloc pipeline)
FillCircle r1001.7ms0 allocs (zero-alloc pipeline)
StrokePath 10seg219ns0 allocs (scratch path reuse, Skia pattern)
SetRGB/SetRGBA<1ns0 allocs (inline solidColor, ADR-036)
Gradient ColorAt33ns0 allocs (pre-sorted stops)

Debugging

Damage Overlay

Visualize which regions are repainted each frame:

GOGPU_DEBUG_DAMAGE=overlay 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

Community Tutorials

We haven't written comprehensive tutorials yet, but the community has started:

🇨🇿 Pavel Tišnovský (root.cz) wrote two in-depth gg tutorials with 54 working examples:

  1. Creating 2D/3D Graphics in Go with GoGPU — 49 min. Paths, Bézier curves, transforms, text, animations.
  2. Part 2: Gradients, SVG & PDF Export — 39 min. Linear/radial/angular gradients, vector export.

Czech language — use Google Translate. Code examples are universal.

Articles


Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

Priority areas:

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

Star History

Star History Chart

License

MIT License — see LICENSE for details.


gg — 2D Graphics for Go

2d-graphics
canvas
creative-coding
drawing
enterprise
go
golang
gpu
graphics
pure-go
rendering

gogpu/gg

Enterprise-grade 2D graphics library for Go. Pure Go, zero CGO. Part of GoGPU ecosystem.

Go

167

529 commits

updated Sep 10, 2026

See the code

README

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.

Seven-Tier GPU Rendering: SDF shapes, convex polygons, stencil+cover paths, MSDF text, Vello compute pipeline, glyph mask cache
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, pixel-perfect mode (SetAntiAlias(false) — dedicated NoAAFiller, Skia/Cairo/tiny-skia pattern), CPU fallback
ShapesRectangles, circles, ellipses, arcs, bezier curves, polygons, stars
TextPure Go font stack (own cmap/hmtx/GSUB/GPOS/gvar/avar/CFF1 parsers, TrueType bytecode interpreter — skrifa golden parity 624/624 diff=0), variable font support (WithVariations — weight/width/slant axes, own gvar/HVAR interpolation), MSDF + glyph mask dual-strategy rendering, TextMode auto-selection, text stroke/outline (StrokeString + TextPath, Skia/Cairo/HTML5 pattern), aliased text (TextModeAliased, Skia kAlias binary masks — GPU + CPU), per-glyph rendering (fractional advances, Skia linearMetrics pattern), OpenType font features (WithFeatures(TabularNums, NoLigatures) — own GSUB/GPOS shaper, 5-10x faster than HarfBuzz), 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 (enterprise auto-hinter + TrueType bytecode interpreter), transform-aware CPU text (scale/rotate/shear), glyph outline caching, emoji support, bidirectional text, 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, HiDPI-aware damage tracking (logical→physical scaling for OS compositor), 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, retained scene lowering (RenderToScene — resolution-independent vector icons via scene graph), thin stroke hinting (pixel-grid snapping for crisp small icons), 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), text batch coalescing (ADR-031: same-style DrawString calls merge into 1 GPU draw call, Skia TextBlob pattern), zero-copy buffer reuse (NewPixmapFromBuffer + ImageView for hot rendering loops), incremental Path.Bounds (Skia pattern), GOGPU_DEBUG_DAMAGE=overlay 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")
}

Common Patterns

// Fill AND stroke the same shape (use FillPreserve to keep the path):
dc.SetHexColor("#3498db")
dc.DrawRoundedRectangle(50, 50, 200, 100, 10)
dc.FillPreserve()               // fill WITHOUT clearing the path
dc.SetHexColor("#2c3e50")
dc.SetLineWidth(3)
dc.Stroke()                     // stroke the same path

// Clear canvas: Clear() fills transparent, ClearWithColor() fills with color:
dc.ClearWithColor(gg.White)     // white background (recommended)
dc.Clear()                      // transparent background (for compositing)

// Load a font (one-liner):
dc.LoadFontFace("arial.ttf", 24)
dc.DrawString("Hello!", 50, 50)

// Scale/rotate around a point:
dc.ScaleAbout(2, 2, 100, 100)   // 2x zoom centered at (100,100)
dc.RotateAbout(0.5, 200, 200)   // rotate 0.5 rad around (200,200)

Rendering

Software Rendering (Default)

The CPU rasterizer automatically selects the optimal algorithm per-path. Curve edges use forward differencing with deviation-based subdivision by default (ADR-063) -- native O(1)/step curve stepping with De Casteljau split for large curves (deviation > 0.1px). Value-type QuadraticEdge/CubicEdge with zero per-edge heap allocations.

AlgorithmTilesBest For
AnalyticFiller (Skia AAA)—Simple paths, small shapes (< 32px). Forward-diff curve edges (Skia updateQuadratic/updateCubic). Pixel-perfect with Chrome/Android.
AnalyticFiller Convex—Convex 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)

Pixel-Perfect Mode (No Anti-Aliasing)

Disable anti-aliasing for crisp, aliased edges — useful for pixel art, retro graphics, technical drawings, and sharp grid lines:

dc.SetAntiAlias(false)          // binary coverage: inside=opaque, outside=transparent
dc.DrawRectangle(10, 10, 100, 50)
dc.Fill()                        // no gray edge pixels
dc.SetAntiAlias(true)           // back to smooth AA

Uses a dedicated integer scanline rasterizer (Skia/tiny-skia pattern) — ~2-3× faster than analytic AA. Works on both CPU and GPU (all backends). Text AA is independent (controlled via SetTextMode).

Text Stroke & Outline

Stroke text outlines for outlined/bordered text effects (Skia/Cairo/HTML5 pattern):

dc.SetLineWidth(3)
dc.SetRGB(0, 0, 0)
dc.StrokeString("Hello", x, y)  // black outline
dc.SetRGB(1, 1, 1)
dc.DrawString("Hello", x, y)    // white fill on top

// Or get text as a Path for custom operations:
path := dc.TextPath("Hello", x, y)

Aliased Text

Pixel-perfect text with binary coverage (Skia SkFont::Edging::kAlias). Works on both GPU and CPU — no GPU required:

dc.SetTextMode(gg.TextModeAliased)  // no gray edge pixels on text
dc.DrawString("Pixel Perfect", x, y)
dc.SavePNG("aliased.png")           // works standalone, no import _ "gg/gpu" needed

Geometry AA (SetAntiAlias) and text AA (SetTextMode) are independent — matching Skia and Cairo separation.

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 ≤64px, 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 ≤64px 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. Forward-diff curve edges with deviation-based subdivision (ADR-063), value-type QuadraticEdge/CubicEdge (zero per-edge heap allocs).
Tile Rasterizersinternal/gpu/ (4×4), internal/gpu/tilecompute/ (16×16)SparseStrips + TileCompute, both ported from Vello
GPU Acceleratorinternal/gpuSeven-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 built-in GSUB/GPOS 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)

// Built-in GSUB/GPOS shaping (ligatures, kerning) is enabled by default.
// No additional setup needed — OwnShaper handles liga, kern features automatically.

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

Blend Modes

29 W3C blend modes for direct Fill/Stroke operations:

// Per-operation blending (Skia/Cairo/Qt pattern):
dc.SetRGB(1, 0, 0) // red background
dc.DrawRectangle(0, 0, 200, 200)
dc.Fill()

dc.SetBlendMode(gg.BlendMultiply)
dc.SetRGB(0, 0, 1) // blue foreground
dc.DrawCircle(100, 100, 80)
dc.Fill() // uses Multiply blending

dc.SetBlendMode(gg.BlendNormal) // reset to default

For isolated compositing with opacity, use layers:

dc.PushLayer(gg.BlendOverlay, 0.7)
// ... draw content ...
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
FillRect 100×100520µs0 allocs (zero-alloc pipeline)
FillCircle r1001.7ms0 allocs (zero-alloc pipeline)
StrokePath 10seg219ns0 allocs (scratch path reuse, Skia pattern)
SetRGB/SetRGBA<1ns0 allocs (inline solidColor, ADR-036)
Gradient ColorAt33ns0 allocs (pre-sorted stops)

Debugging

Damage Overlay

Visualize which regions are repainted each frame:

GOGPU_DEBUG_DAMAGE=overlay 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

Community Tutorials

We haven't written comprehensive tutorials yet, but the community has started:

🇨🇿 Pavel Tišnovský (root.cz) wrote two in-depth gg tutorials with 54 working examples:

  1. Creating 2D/3D Graphics in Go with GoGPU — 49 min. Paths, Bézier curves, transforms, text, animations.
  2. Part 2: Gradients, SVG & PDF Export — 39 min. Linear/radial/angular gradients, vector export.

Czech language — use Google Translate. Code examples are universal.

Articles


Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

Priority areas:

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

Star History

Star History Chart

License

MIT License — see LICENSE for details.


gg — 2D Graphics for Go

2d-graphics
canvas
creative-coding
drawing
enterprise
go
golang
gpu
graphics
pure-go
rendering

Project health

Latest release

v0.52.5 · Aug 25, 2026

Releases this year

198

Significant stargazers

Robin

341 followers · starred Dec 2025

Milos Gajdos

427 followers · starred Apr 2026

James Miller

160 followers · starred Sep 2026

yanghy

11 followers · starred Apr 2026