draw optimizations
This commit is contained in:
+118
-44
@@ -20,9 +20,9 @@ type drawItem struct {
|
||||
styleID StyleID
|
||||
|
||||
// Exactly one of these is set.
|
||||
p *Point
|
||||
c *Circle
|
||||
l *Line
|
||||
p Point
|
||||
c Circle
|
||||
l Line
|
||||
}
|
||||
|
||||
// drawPlanSinglePass renders a plan using a single ordered pass per tile.
|
||||
@@ -31,7 +31,9 @@ type drawItem struct {
|
||||
// allowWrap controls torus behavior:
|
||||
// - true: circles/points produce wrap copies, lines use torus-shortest segments
|
||||
// - false: no copies, lines drawn directly as stored
|
||||
func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allowWrap bool) {
|
||||
// tileClipEnabled controls whether per-tile ClipRect is applied.
|
||||
// When an outer clip is already set (e.g. dirty rect), disable tile clips for speed.
|
||||
func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allowWrap bool, tileClipEnabled bool, isDirtyPass bool) {
|
||||
var lastStyleID StyleID = StyleIDInvalid
|
||||
var lastStyle Style
|
||||
|
||||
@@ -41,11 +43,9 @@ func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allo
|
||||
}
|
||||
s, ok := w.styles.Get(styleID)
|
||||
if !ok {
|
||||
// Unknown style ID is a programming/config error.
|
||||
panic("render: unknown style ID")
|
||||
}
|
||||
|
||||
// Apply style state. Some fields may be nil intentionally.
|
||||
if s.FillColor != nil {
|
||||
drawer.SetFillColor(s.FillColor)
|
||||
}
|
||||
@@ -56,7 +56,6 @@ func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allo
|
||||
if len(s.StrokeDashes) > 0 {
|
||||
drawer.SetDash(s.StrokeDashes...)
|
||||
} else {
|
||||
// Ensure solid line when switching from dashed style.
|
||||
drawer.SetDash()
|
||||
}
|
||||
drawer.SetDashOffset(s.StrokeDashOffset)
|
||||
@@ -70,52 +69,61 @@ func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allo
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect items for this tile.
|
||||
items := make([]drawItem, 0, len(td.Candidates))
|
||||
// Per-tile clip is optional. When outer-clip is used (dirty rect),
|
||||
// tileClipEnabled must be false to avoid resetting the outer clip.
|
||||
if tileClipEnabled {
|
||||
drawer.Save()
|
||||
drawer.ResetClip()
|
||||
drawer.ClipRect(float64(td.ClipX), float64(td.ClipY), float64(td.ClipW), float64(td.ClipH))
|
||||
}
|
||||
|
||||
items := w.scratchDrawItems[:0]
|
||||
if cap(items) < len(td.Candidates) {
|
||||
items = make([]drawItem, 0, len(td.Candidates))
|
||||
}
|
||||
|
||||
for _, it := range td.Candidates {
|
||||
id := it.ID()
|
||||
cur, ok := w.objects[id]
|
||||
if !ok {
|
||||
// Stale grid entry (object removed). Skip.
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := cur.(type) {
|
||||
case Point:
|
||||
vv := v
|
||||
items = append(items, drawItem{
|
||||
kind: drawKindPoint,
|
||||
priority: vv.Priority,
|
||||
id: vv.Id,
|
||||
styleID: vv.StyleID,
|
||||
p: &vv,
|
||||
priority: v.Priority,
|
||||
id: v.Id,
|
||||
styleID: v.StyleID,
|
||||
p: v,
|
||||
})
|
||||
case Circle:
|
||||
vv := v
|
||||
items = append(items, drawItem{
|
||||
kind: drawKindCircle,
|
||||
priority: vv.Priority,
|
||||
id: vv.Id,
|
||||
styleID: vv.StyleID,
|
||||
c: &vv,
|
||||
priority: v.Priority,
|
||||
id: v.Id,
|
||||
styleID: v.StyleID,
|
||||
c: v,
|
||||
})
|
||||
case Line:
|
||||
vv := v
|
||||
items = append(items, drawItem{
|
||||
kind: drawKindLine,
|
||||
priority: vv.Priority,
|
||||
id: vv.Id,
|
||||
styleID: vv.StyleID,
|
||||
l: &vv,
|
||||
priority: v.Priority,
|
||||
id: v.Id,
|
||||
styleID: v.StyleID,
|
||||
l: v,
|
||||
})
|
||||
default:
|
||||
// Unknown map items should not exist.
|
||||
panic("render: unknown map item type")
|
||||
}
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
if tileClipEnabled {
|
||||
drawer.Restore()
|
||||
}
|
||||
w.scratchDrawItems = items[:0]
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -130,27 +138,93 @@ func (w *World) drawPlanSinglePass(drawer PrimitiveDrawer, plan RenderPlan, allo
|
||||
return a.id < b.id
|
||||
})
|
||||
|
||||
drawer.Save()
|
||||
drawer.ClipRect(float64(td.ClipX), float64(td.ClipY), float64(td.ClipW), float64(td.ClipH))
|
||||
// If this is not a dirty pass (full redraw), keep the old behavior for lines:
|
||||
// stroke per segment. This is usually faster for gg on huge scenes.
|
||||
if !isDirtyPass {
|
||||
for i := 0; i < len(items); i++ {
|
||||
di := items[i]
|
||||
applyStyle(di.styleID)
|
||||
|
||||
for _, di := range items {
|
||||
applyStyle(di.styleID)
|
||||
|
||||
switch di.kind {
|
||||
case drawKindPoint:
|
||||
w.drawPointInTile(drawer, plan, td, *di.p, allowWrap, lastStyle)
|
||||
|
||||
case drawKindCircle:
|
||||
w.drawCircleInTile(drawer, plan, td, *di.c, allowWrap, lastStyle)
|
||||
|
||||
case drawKindLine:
|
||||
w.drawLineInTile(drawer, plan, td, *di.l, allowWrap)
|
||||
|
||||
default:
|
||||
panic("render: unknown draw kind")
|
||||
switch di.kind {
|
||||
case drawKindPoint:
|
||||
w.drawPointInTile(drawer, plan, td, di.p, allowWrap, lastStyle)
|
||||
case drawKindCircle:
|
||||
w.drawCircleInTile(drawer, plan, td, di.c, allowWrap, lastStyle)
|
||||
case drawKindLine:
|
||||
// Old behavior: drawLineInTile includes Stroke() per segment.
|
||||
w.drawLineInTile(drawer, plan, td, di.l, allowWrap)
|
||||
default:
|
||||
panic("render: unknown draw kind")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Dirty pass: batch lines to reduce overhead while panning.
|
||||
inLineRun := false
|
||||
var lineRunStyleID StyleID
|
||||
lineSegCount := 0
|
||||
|
||||
flushLineRun := func() {
|
||||
if !inLineRun {
|
||||
return
|
||||
}
|
||||
drawer.Stroke()
|
||||
inLineRun = false
|
||||
lineSegCount = 0
|
||||
}
|
||||
|
||||
for i := 0; i < len(items); i++ {
|
||||
di := items[i]
|
||||
|
||||
if inLineRun {
|
||||
if di.kind != drawKindLine || di.styleID != lineRunStyleID {
|
||||
flushLineRun()
|
||||
}
|
||||
}
|
||||
|
||||
switch di.kind {
|
||||
case drawKindLine:
|
||||
if !inLineRun {
|
||||
lineRunStyleID = di.styleID
|
||||
applyStyle(lineRunStyleID)
|
||||
inLineRun = true
|
||||
} else {
|
||||
// style matches by construction; keep style state valid if code changes later
|
||||
applyStyle(di.styleID)
|
||||
}
|
||||
|
||||
added := w.drawLineInTilePath(drawer, plan, td, di.l, allowWrap)
|
||||
lineSegCount += added
|
||||
|
||||
if lineSegCount >= maxLineSegmentsPerStroke {
|
||||
drawer.Stroke()
|
||||
lineSegCount = 0
|
||||
// keep run active
|
||||
}
|
||||
|
||||
case drawKindPoint:
|
||||
flushLineRun()
|
||||
applyStyle(di.styleID)
|
||||
w.drawPointInTile(drawer, plan, td, di.p, allowWrap, lastStyle)
|
||||
|
||||
case drawKindCircle:
|
||||
flushLineRun()
|
||||
applyStyle(di.styleID)
|
||||
w.drawCircleInTile(drawer, plan, td, di.c, allowWrap, lastStyle)
|
||||
|
||||
default:
|
||||
flushLineRun()
|
||||
panic("render: unknown draw kind")
|
||||
}
|
||||
}
|
||||
|
||||
flushLineRun()
|
||||
}
|
||||
|
||||
drawer.Restore()
|
||||
if tileClipEnabled {
|
||||
drawer.Restore()
|
||||
}
|
||||
|
||||
// Reuse buffer for next tile.
|
||||
w.scratchDrawItems = items[:0]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user