package world import ( "image" "image/color" "testing" "github.com/fogleman/gg" ) type benchBgTheme struct { img image.Image anchor BackgroundAnchorMode tileMode BackgroundTileMode scaleMode BackgroundScaleMode } func (t benchBgTheme) ID() string { return "benchbg" } func (t benchBgTheme) Name() string { return "benchbg" } func (t benchBgTheme) BackgroundColor() color.Color { return color.RGBA{A: 255} } func (t benchBgTheme) BackgroundImage() image.Image { return t.img } func (t benchBgTheme) BackgroundTileMode() BackgroundTileMode { return t.tileMode } func (t benchBgTheme) BackgroundScaleMode() BackgroundScaleMode { return t.scaleMode } func (t benchBgTheme) BackgroundAnchorMode() BackgroundAnchorMode { return t.anchor } func (t benchBgTheme) PointStyle() Style { return Style{FillColor: color.RGBA{A: 255}, PointRadiusPx: 2} } func (t benchBgTheme) LineStyle() Style { return Style{StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1} } func (t benchBgTheme) CircleStyle() Style { return Style{FillColor: color.RGBA{A: 255}, StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1} } func (t benchBgTheme) PointClassOverride(PointClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (t benchBgTheme) LineClassOverride(LineClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (t benchBgTheme) CircleClassOverride(CircleClassID) (StyleOverride, bool) { return StyleOverride{}, false } func BenchmarkRender_IncrementalPan_NoBackground(b *testing.B) { w := NewWorld(600, 600) w.IndexOnViewportChange(1200, 800, 1.0) // Some primitives to keep it realistic but not dominant. for i := 0; i < 200; i++ { _, _ = w.AddPoint(float64(i%600), float64((i*7)%600)) } w.Reindex() dc := gg.NewContext(1200, 800) drawer := &GGDrawer{DC: dc} params := RenderParams{ ViewportWidthPx: 1000, ViewportHeightPx: 700, MarginXPx: 250, MarginYPx: 175, CameraXWorldFp: 300 * SCALE, CameraYWorldFp: 300 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ Incremental: &IncrementalPolicy{ AllowShiftOnly: false, CoalesceUpdates: false, MaxCatchUpAreaPx: 0, RenderBudgetMs: 0, }, }, } // Initial render (commit state). _ = w.Render(drawer, params) b.ResetTimer() for i := 0; i < b.N; i++ { params.CameraXWorldFp += 1 * SCALE _ = w.Render(drawer, params) } } func BenchmarkRender_IncrementalPan_BackgroundRepeat_WorldAnchor_ScaleNone(b *testing.B) { benchRenderBg(b, BackgroundAnchorWorld, BackgroundTileRepeat, BackgroundScaleNone) } func BenchmarkRender_IncrementalPan_BackgroundRepeat_WorldAnchor_ScaleFit(b *testing.B) { benchRenderBg(b, BackgroundAnchorWorld, BackgroundTileRepeat, BackgroundScaleFit) } func BenchmarkRender_IncrementalPan_BackgroundRepeat_ViewportAnchor_ScaleNone(b *testing.B) { benchRenderBg(b, BackgroundAnchorViewport, BackgroundTileRepeat, BackgroundScaleNone) } func benchRenderBg(b *testing.B, anchor BackgroundAnchorMode, tile BackgroundTileMode, scale BackgroundScaleMode) { w := NewWorld(600, 600) w.IndexOnViewportChange(1200, 800, 1.0) for i := 0; i < 200; i++ { _, _ = w.AddPoint(float64(i%600), float64((i*7)%600)) } w.Reindex() // Background tile (RGBA) — typical texture size. bg := image.NewRGBA(image.Rect(0, 0, 96, 96)) // Make it semi-transparent so draw.Over has real work. for y := 0; y < 96; y++ { for x := 0; x < 96; x++ { bg.SetRGBA(x, y, color.RGBA{R: 255, G: 255, B: 255, A: 18}) } } w.SetTheme(benchBgTheme{img: bg, anchor: anchor, tileMode: tile, scaleMode: scale}) dc := gg.NewContext(1200, 800) drawer := &GGDrawer{DC: dc} params := RenderParams{ ViewportWidthPx: 1000, ViewportHeightPx: 700, MarginXPx: 250, MarginYPx: 175, CameraXWorldFp: 300 * SCALE, CameraYWorldFp: 300 * SCALE, CameraZoom: 1.0, Options: &RenderOptions{ Incremental: &IncrementalPolicy{ AllowShiftOnly: false, CoalesceUpdates: false, MaxCatchUpAreaPx: 0, RenderBudgetMs: 0, }, }, } _ = w.Render(drawer, params) b.ResetTimer() for i := 0; i < b.N; i++ { params.CameraXWorldFp += 1 * SCALE _ = w.Render(drawer, params) } }