Skip to content

refactor: delegate scene.Renderer rasterization to gg.SoftwareRenderer #124

@kolkov

Description

@kolkov

Summary

scene.Renderer currently has its own simplified CPU rasterizer that was written when the scene package was first created — at that time, gg.SoftwareRenderer with analytic AA and the GPU acceleration pipeline (GPUAccelerator, SDF, stencil-then-cover, MSDF text) did not yet exist. The rasterizer was a minimal implementation to get the scene graph working.

Now that gg has a full-featured rendering infrastructure — analytic AA, proper stroke expansion with caps/joins, GPU acceleration, premultiplied alpha compositing — the scene renderer should delegate pixel rendering to gg.SoftwareRenderer instead of maintaining its own incomplete rasterizer.

Reported by contributor in #116: white background destroyed, circle stroke invisible.

Current Problems

The historical rasterizer has 6 known issues:

Bug Location Description
No anti-aliasing renderer.go:527-538 Binary winding number test, jagged edges
Stroke ignores curves renderer.go:572-594 Only handles LineTo, silently drops CubicTo/QuadTo
Background destroyed renderer.go:384 clear(tile.Data) wipes user's pre-cleared background
No alpha compositing renderer.go:687 copy() instead of source-over blending
Layers/clips stubbed renderer.go:459-474 PushLayer/BeginClip are no-ops
Only solid brushes renderer.go:485 Returns immediately for non-solid brushes

Architecture Decision

Research of 4 enterprise scene graph systems (Qt Quick, Skia/SkPicture, Vello, Flutter/Impeller) confirms a universal pattern: the scene graph layer handles orchestration (recording, tiles, dirty regions, parallelism), while pixel rendering is always delegated to an immediate-mode backend. None of them reimplement rasterization in the scene graph layer.

Approach: Replace the rasterizer, not the architecture.

  • Keep: TileGrid, WorkerPool, DirtyRegion, LayerCache, Encoding/Decoder — all well-designed
  • Replace: fillPathOnTile, strokePathOnTile, compositeTile — delegate to gg.SoftwareRenderer

This also opens the path to GPU acceleration in the scene renderer — since gg.Context already routes through GPUAccelerator when available.

Implementation Phases

  • Phase 1: Core delegation — Replace fill/stroke/composite with SoftwareRenderer delegation (~200 lines)
  • Phase 2: Pool optimizationsync.Pool for per-tile SoftwareRenderer/Pixmap reuse (~100 lines)
  • Phase 3: Layer & clip support — Implement PushLayer/PopLayer, BeginClip/EndClip (~300 lines)
  • Phase 4: GPU acceleration — Route shapes through GPUAccelerator when available (~150 lines)
  • Phase 5: Image & gradient — TagImage rendering, gradient brush support (~200 lines)

Zero API Changes

All modifications are internal to renderer.go. Public API (NewRenderer, Render, RenderDirty, Stats) remains unchanged.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions