render/scene.go
Functions
func Circle
func (s *Scene) Circle(cx, cy, r float64) {
// Cubic Bezier circle approximation using kappa = 4 * (sqrt(2) - 1) / 3
const kappa = 0.5522847498307936
k := r * kappa
s.MoveTo(cx+r, cy)
s.CubicTo(cx+r, cy+k, cx+k, cy+r, cx, cy+r)
s.CubicTo(cx-k, cy+r, cx-r, cy+k, cx-r, cy)
s.CubicTo(cx-r, cy-k, cx-k, cy-r, cx, cy-r)
s.CubicTo(cx+k, cy-r, cx+r, cy-k, cx+r, cy)
}
func Clear
Clear adds a clear operation to fill the entire target.
func (s *Scene) Clear(c color.Color) {
s.commands = append(s.commands, drawCommand{
op: opClear,
color: c,
})
}
func ClearDirty
ClearDirty resets the dirty state after rendering.
This should be called after each render pass.
func (s *Scene) ClearDirty() {
s.dirtyRects = s.dirtyRects[:0]
s.fullRedraw = false
}
func ClosePath
ClosePath closes the current subpath.
func (s *Scene) ClosePath() {
s.currentPath.verbs = append(s.currentPath.verbs, raster.Close)
}
func CommandCount
CommandCount returns the number of drawing commands in the scene.
func (s *Scene) CommandCount() int {
return len(s.commands)
}
func CubicTo
CubicTo draws a cubic Bezier curve.
func (s *Scene) CubicTo(c1x, c1y, c2x, c2y, x, y float64) {
s.currentPath.verbs = append(s.currentPath.verbs, raster.CubicTo)
s.currentPath.points = append(s.currentPath.points,
float32(c1x), float32(c1y),
float32(c2x), float32(c2y),
float32(x), float32(y))
}
func DirtyRects
DirtyRects returns the accumulated dirty rectangles.
Returns nil if the scene needs a full redraw (check NeedsFullRedraw first).
The returned slice should not be modified by the caller.
func (s *Scene) DirtyRects() []DirtyRect {
if s.fullRedraw {
return nil
}
return s.dirtyRects
}
func Fill
Fill fills the current path and clears it.
func (s *Scene) Fill() {
if len(s.currentPath.verbs) == 0 {
return
}
// Snapshot the current path
path := s.snapshotPath()
s.commands = append(s.commands, drawCommand{
op: opFill,
path: path,
color: s.currentFillColor,
fillRule: s.currentFillRule,
})
// Clear the current path
s.currentPath = pathBuilder{}
}
func HasDirtyRegions
HasDirtyRegions returns true if there are any dirty regions to redraw.
This includes both individual rects and full redraw state.
func (s *Scene) HasDirtyRegions() bool {
return s.fullRedraw || len(s.dirtyRects) > 0
}
func Invalidate
Invalidate marks a rectangular region as needing redraw.
This is used for damage tracking to enable efficient partial redraws.
If the accumulated dirty rects exceed maxDirtyRects, the scene switches
to full redraw mode for efficiency.
func (s *Scene) Invalidate(rect DirtyRect) {
if s.fullRedraw {
return // Already in full redraw mode
}
// Validate rect has positive dimensions
if rect.Width <= 0 || rect.Height <= 0 {
return
}
s.dirtyRects = append(s.dirtyRects, rect)
// If too many rects, switch to full redraw
if len(s.dirtyRects) > maxDirtyRects {
s.fullRedraw = true
s.dirtyRects = s.dirtyRects[:0] // Clear rects since we're doing full redraw
}
}
func InvalidateAll
InvalidateAll marks the entire scene as needing redraw.
This forces a full redraw on the next render pass.
func (s *Scene) InvalidateAll() {
s.fullRedraw = true
s.dirtyRects = s.dirtyRects[:0] // Clear individual rects
}
func IsEmpty
IsEmpty returns true if the scene has no commands.
func (s *Scene) IsEmpty() bool {
return len(s.commands) == 0
}
func IsEmpty
pathBuilder implements raster.PathLike for use with EdgeBuilder.
func (p *pathBuilder) IsEmpty() bool {
return len(p.verbs) == 0
}
func LineTo
LineTo draws a line from the current point to the given point.
func (s *Scene) LineTo(x, y float64) {
s.currentPath.verbs = append(s.currentPath.verbs, raster.LineTo)
s.currentPath.points = append(s.currentPath.points, float32(x), float32(y))
}
func MoveTo
MoveTo starts a new subpath at the given point.
func (s *Scene) MoveTo(x, y float64) {
s.currentPath.verbs = append(s.currentPath.verbs, raster.MoveTo)
s.currentPath.points = append(s.currentPath.points, float32(x), float32(y))
}
func NeedsFullRedraw
NeedsFullRedraw returns true if the scene should be fully redrawn.
This is true when InvalidateAll was called or when too many dirty rects
have accumulated (more than maxDirtyRects).
func (s *Scene) NeedsFullRedraw() bool {
return s.fullRedraw
}
func NewScene
NewScene creates a new empty Scene.
func NewScene() *Scene {
return &Scene{
commands: make([]drawCommand, 0, 16),
currentFillColor: color.Black,
currentStrokeColor: color.Black,
currentStrokeWidth: 1.0,
currentFillRule: raster.FillRuleNonZero,
}
}
func Points
func (p *pathBuilder) Points() []float32 {
return p.points
}
func QuadTo
QuadTo draws a quadratic Bezier curve.
func (s *Scene) QuadTo(cx, cy, x, y float64) {
s.currentPath.verbs = append(s.currentPath.verbs, raster.QuadTo)
s.currentPath.points = append(s.currentPath.points,
float32(cx), float32(cy),
float32(x), float32(y))
}
func Rectangle
Rectangle adds a rectangle to the current path.
func (s *Scene) Rectangle(x, y, width, height float64) {
s.MoveTo(x, y)
s.LineTo(x+width, y)
s.LineTo(x+width, y+height)
s.LineTo(x, y+height)
s.ClosePath()
}
func Reset
Reset clears the scene for reuse.
func (s *Scene) Reset() {
s.commands = s.commands[:0]
s.currentPath = pathBuilder{}
s.currentFillColor = color.Black
s.currentStrokeColor = color.Black
s.currentStrokeWidth = 1.0
s.currentFillRule = raster.FillRuleNonZero
s.ClearDirty()
}
func SetFillColor
SetFillColor sets the color for subsequent fill operations.
func (s *Scene) SetFillColor(c color.Color) {
s.currentFillColor = c
}
func SetFillRule
SetFillRule sets the fill rule for subsequent fill operations.
func (s *Scene) SetFillRule(rule raster.FillRule) {
s.currentFillRule = rule
}
func SetStrokeColor
SetStrokeColor sets the color for subsequent stroke operations.
func (s *Scene) SetStrokeColor(c color.Color) {
s.currentStrokeColor = c
}
func SetStrokeWidth
SetStrokeWidth sets the width for subsequent stroke operations.
func (s *Scene) SetStrokeWidth(width float64) {
s.currentStrokeWidth = width
}
func Stroke
Stroke strokes the current path and clears it.
func (s *Scene) Stroke() {
if len(s.currentPath.verbs) == 0 {
return
}
// Snapshot the current path
path := s.snapshotPath()
s.commands = append(s.commands, drawCommand{
op: opStroke,
path: path,
color: s.currentStrokeColor,
width: s.currentStrokeWidth,
})
// Clear the current path
s.currentPath = pathBuilder{}
}
func Verbs
func (p *pathBuilder) Verbs() []raster.PathVerb {
return p.verbs
}
Structs
type DirtyRect struct
DirtyRect represents a region that needs redraw.
Used for damage tracking to enable efficient partial redraws.
type DirtyRect struct {
X, Y, Width, Height float64
}
type Scene struct
Scene represents a retained-mode drawing tree.
Unlike immediate-mode drawing (Context.DrawCircle, etc.), a Scene captures
drawing commands that can be:
- Rendered multiple times without rebuilding
- Partially invalidated for efficient UI updates
- Optimized by the renderer for batching
Scene is designed to work with the Renderer interface, allowing both
CPU and GPU rendering backends to process the same scene data.
Example:
scene := render.NewScene()
scene.SetFillColor(color.RGBA{255, 0, 0, 255})
scene.MoveTo(100, 50)
scene.LineTo(150, 150)
scene.LineTo(50, 150)
scene.ClosePath()
scene.Fill()
// Render to any target
renderer.Render(target1, scene)
renderer.Render(target2, scene)
type Scene struct {
// commands stores the drawing command stream.
commands []drawCommand
// currentPath builds the current path being constructed.
currentPath pathBuilder
// currentFillColor is the color for fill operations.
currentFillColor color.Color
// currentStrokeColor is the color for stroke operations.
currentStrokeColor color.Color
// currentStrokeWidth is the width for stroke operations.
currentStrokeWidth float64
// currentFillRule is the fill rule for fill operations.
currentFillRule raster.FillRule
// Damage tracking for efficient partial redraws
dirtyRects []DirtyRect
fullRedraw bool
}
Circle adds a circle to the current path using cubic Bezier approximation.