-
Notifications
You must be signed in to change notification settings - Fork 2
Description
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 togg.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
SoftwareRendererdelegation (~200 lines) - Phase 2: Pool optimization —
sync.Poolfor 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
- Internal research:
SCENE-RENDERER-DELEGATION-RESEARCH.md(covers Qt/Skia/Vello/Flutter delegation patterns) - Contributor report: SceneBuilder.WithTransform causes invisible rendering #116 (comment from 2026-02-23)