draw optimizations
This commit is contained in:
+63
-32
@@ -15,6 +15,13 @@ const (
|
||||
RenderLayerLines
|
||||
)
|
||||
|
||||
const (
|
||||
drawPlanSinglePassClipEnabled = false
|
||||
|
||||
// best value according to BenchmarkDrawPlanSinglePass_Lines_GG
|
||||
maxLineSegmentsPerStroke = 32
|
||||
)
|
||||
|
||||
// RenderOptions controls which layers are rendered and their order.
|
||||
// If Layers is empty, the default order is: Points, Circles, Lines.
|
||||
type RenderOptions struct {
|
||||
@@ -174,11 +181,7 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
var bg color.Color = color.RGBA{A: 255} // default black
|
||||
|
||||
if params.Options != nil && params.Options.BackgroundColor != nil {
|
||||
if v, ok := params.Options.BackgroundColor.(color.RGBA); !ok {
|
||||
panic("Options.BackgroundColor is not color.RGBA type")
|
||||
} else {
|
||||
bg = v
|
||||
}
|
||||
bg = params.Options.BackgroundColor
|
||||
} else {
|
||||
tc := w.Theme().BackgroundColor()
|
||||
if alphaNonZero(tc) {
|
||||
@@ -225,6 +228,30 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
policy = *params.Options.Incremental
|
||||
}
|
||||
|
||||
// Helper: draw one dirty rect with outer clip, using an already prepared dirtyPlan.
|
||||
// IMPORTANT: dirtyPlan must be built from the FULL set of dirty rects (union),
|
||||
// not from a single rect, to avoid missing primitives on diagonal pans.
|
||||
drawDirtyRect := func(dirtyPlan RenderPlan, r RectPx) error {
|
||||
if r.W <= 0 || r.H <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
drawer.Save()
|
||||
drawer.ResetClip()
|
||||
drawer.ClipRect(float64(r.X), float64(r.Y), float64(r.W), float64(r.H))
|
||||
|
||||
// Clear + background in the same clip.
|
||||
drawer.ClearRectTo(r.X, r.Y, r.W, r.H, bg)
|
||||
w.drawBackground(drawer, params, r)
|
||||
|
||||
// Draw with outer clip only; do not rebuild plan per-rect.
|
||||
// isDirtyPass MUST be true here.
|
||||
w.drawPlanSinglePass(drawer, dirtyPlan, allowWrap, drawPlanSinglePassClipEnabled, true)
|
||||
|
||||
drawer.Restore()
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Try incremental path first when state is initialized and geometry matches ---
|
||||
dxPx, dyPx, derr := w.ComputePanShiftPx(params)
|
||||
if derr == nil {
|
||||
@@ -245,23 +272,25 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
// If we accumulated dirty regions during shift-only frames, redraw them now (bounded).
|
||||
if len(w.renderState.pendingDirty) > 0 {
|
||||
toDraw, remaining := takeCatchUpRects(w.renderState.pendingDirty, policy.MaxCatchUpAreaPx)
|
||||
w.renderState.pendingDirty = remaining
|
||||
|
||||
if len(toDraw) > 0 {
|
||||
for _, r := range toDraw {
|
||||
drawer.ClearRectTo(r.X, r.Y, r.W, r.H, bg)
|
||||
w.drawBackground(drawer, params, r)
|
||||
}
|
||||
|
||||
plan, err := w.buildRenderPlanStageA(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catchUpPlan := planRestrictedToDirtyRects(plan, toDraw)
|
||||
w.drawPlanSinglePass(drawer, catchUpPlan, allowWrap)
|
||||
if len(toDraw) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.renderState.pendingDirty = remaining
|
||||
plan, err := w.buildRenderPlanStageA(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build once for the whole set of catch-up rects (union), then clip per rect.
|
||||
catchUpPlan := planRestrictedToDirtyRects(plan, toDraw)
|
||||
|
||||
for _, r := range toDraw {
|
||||
if err := drawDirtyRect(catchUpPlan, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -276,7 +305,7 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
}
|
||||
w.renderState.pendingDirty = moved
|
||||
}
|
||||
// C5: shift backing pixels, then redraw only dirty strips.
|
||||
// Shift backing pixels first.
|
||||
drawer.CopyShift(inc.DxPx, inc.DyPx)
|
||||
|
||||
overBudget := false
|
||||
@@ -294,17 +323,9 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// [ ] Сразу после overBudget вычисления и до построения dirtyPlan
|
||||
// Draw both the newly exposed strips and any previously deferred dirty regions.
|
||||
// Always redraw newly exposed strips fully.
|
||||
// Under budget: draw newly exposed strips immediately, plus bounded catch-up.
|
||||
dirtyToDraw := inc.Dirty
|
||||
|
||||
for _, r := range dirtyToDraw {
|
||||
drawer.ClearRectTo(r.X, r.Y, r.W, r.H, bg)
|
||||
w.drawBackground(drawer, params, r)
|
||||
}
|
||||
|
||||
// Additionally redraw a bounded portion of deferred dirty regions.
|
||||
if len(w.renderState.pendingDirty) > 0 {
|
||||
catchUp, remaining := takeCatchUpRects(w.renderState.pendingDirty, policy.MaxCatchUpAreaPx)
|
||||
@@ -312,14 +333,24 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
w.renderState.pendingDirty = remaining
|
||||
}
|
||||
|
||||
if len(dirtyToDraw) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
plan, err := w.buildRenderPlanStageA(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dirtyPlan := planRestrictedToDirtyRects(plan, dirtyToDraw)
|
||||
w.drawPlanSinglePass(drawer, dirtyPlan, allowWrap)
|
||||
|
||||
// State already updated by ComputePanShiftPx (lastWorldRect advanced).
|
||||
// Build once for the union of all dirty rects.
|
||||
dirtyPlan := planRestrictedToDirtyRects(plan, dirtyToDraw)
|
||||
|
||||
// Draw per-rect with outer clip; background/clear done inside helper.
|
||||
for _, r := range dirtyToDraw {
|
||||
if err := drawDirtyRect(dirtyPlan, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case IncrementalFullRedraw:
|
||||
@@ -337,7 +368,7 @@ func (w *World) Render(drawer PrimitiveDrawer, params RenderParams) error {
|
||||
|
||||
drawer.ClearAllTo(bg)
|
||||
w.drawBackground(drawer, params, RectPx{X: 0, Y: 0, W: params.CanvasWidthPx(), H: params.CanvasHeightPx()})
|
||||
w.drawPlanSinglePass(drawer, plan, allowWrap)
|
||||
w.drawPlanSinglePass(drawer, plan, allowWrap, drawPlanSinglePassClipEnabled, true)
|
||||
return w.CommitFullRedrawState(params)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user