package world import ( "image/color" "testing" "github.com/fogleman/gg" "github.com/stretchr/testify/require" ) func BenchmarkDrawPlanSinglePass_Lines_GG(b *testing.B) { w := NewWorld(600, 600) w.IndexOnViewportChange(1000, 700, 1.0) // Make a lot of lines, including ones that likely wrap. for i := 0; i < 4000; i++ { x1 := float64(i % 600) y1 := float64((i * 7) % 600) x2 := float64((i*13 + 500) % 600) // shift to create various deltas y2 := float64((i*17 + 300) % 600) _, _ = w.AddLine(x1, y1, x2, y2) } w.Reindex() params := RenderParams{ ViewportWidthPx: 1000, ViewportHeightPx: 700, MarginXPx: 250, MarginYPx: 175, CameraXWorldFp: 300 * SCALE, CameraYWorldFp: 300 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ BackgroundColor: color.RGBA{A: 255}, }, } plan, err := w.buildRenderPlanStageA(params) if err != nil { b.Fatalf("build plan: %v", err) } dc := gg.NewContext(params.CanvasWidthPx(), params.CanvasHeightPx()) drawer := &GGDrawer{DC: dc} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { w.drawPlanSinglePass(drawer, plan, true, drawPlanSinglePassClipEnabled, false) } } func BenchmarkDrawPlanSinglePass_Lines_Fake(b *testing.B) { w := NewWorld(600, 600) w.IndexOnViewportChange(1000, 700, 1.0) for i := 0; i < 4000; i++ { x1 := float64(i % 600) y1 := float64((i * 7) % 600) x2 := float64((i*13 + 500) % 600) y2 := float64((i*17 + 300) % 600) _, _ = w.AddLine(x1, y1, x2, y2) } w.Reindex() params := RenderParams{ ViewportWidthPx: 1000, ViewportHeightPx: 700, MarginXPx: 250, MarginYPx: 175, CameraXWorldFp: 300 * SCALE, CameraYWorldFp: 300 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ BackgroundColor: color.RGBA{A: 255}, }, } plan, err := w.buildRenderPlanStageA(params) if err != nil { b.Fatalf("build plan: %v", err) } drawer := &fakePrimitiveDrawer{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { // Reset command log so it doesn't grow forever and dominate allocations. drawer.Reset() w.drawPlanSinglePass(drawer, plan, true, drawPlanSinglePassClipEnabled, false) } } func TestRender_IncrementalShift_UsesOuterClip_NotPerTileClips(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.IndexOnViewportChange(100, 80, 1.0) w.resetGrid(2 * SCALE) _, _ = w.AddPoint(5, 5) w.Reindex() params := RenderParams{ ViewportWidthPx: 100, ViewportHeightPx: 80, MarginXPx: 25, MarginYPx: 20, CameraXWorldFp: 5 * SCALE, CameraYWorldFp: 5 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ Incremental: &IncrementalPolicy{AllowShiftOnly: false}, }, } // First render initializes state. d1 := &fakePrimitiveDrawer{} require.NoError(t, w.Render(d1, params)) // Small pan. params2 := params params2.CameraXWorldFp += 1 * SCALE d2 := &fakePrimitiveDrawer{} require.NoError(t, w.Render(d2, params2)) // Expect very few ClipRect calls (dirty strips count), not per tile. clipCmds := d2.CommandsByName("ClipRect") require.NotEmpty(t, clipCmds) require.LessOrEqual(t, len(clipCmds), 4) } func TestRender_BatchesConsecutiveLinesByStyleID(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.IndexOnViewportChange(100, 80, 1.0) // Two lines with default style, same priority. _, _ = w.AddLine(1, 1, 8, 1) _, _ = w.AddLine(1, 2, 8, 2) w.Reindex() params := RenderParams{ ViewportWidthPx: 100, ViewportHeightPx: 80, MarginXPx: 25, MarginYPx: 20, CameraXWorldFp: 5 * SCALE, CameraYWorldFp: 5 * SCALE, CameraZoom: 1.0, } d := &fakePrimitiveDrawer{} require.NoError(t, w.Render(d, params)) // We expect at least two AddLine, but only 1 Stroke for that run in a tile. adds := d.CommandsByName("AddLine") strokes := d.CommandsByName("Stroke") require.GreaterOrEqual(t, len(adds), 2) require.GreaterOrEqual(t, len(strokes), 1) // Stronger: within any consecutive group of AddLine commands, count strokes <= 1. // (Keep it loose to avoid depending on tile partitioning.) }