1package renderer
2
3import "testing"
4
5func markBenchmarkFrame(f *Frame) {
6 for y := range f.Height {
7 for x := range f.Width {
8 f.Set(x, y, Cell{Rune: rune('A' + (y*f.Width+x)%26), Style: DefaultStyle()})
9 }
10 }
11}
12
13func BenchmarkRendererFullFrameDraw(b *testing.B) {
14 frame := NewFrame(120, 40)
15 markBenchmarkFrame(&frame)
16 r := New(Capabilities{})
17 damage := []Damage{FullRedraw()}
18 b.ReportAllocs()
19 var outBytes int64
20 for b.Loop() {
21 r.Reset()
22 out, err := r.Draw(frame, damage)
23 if err != nil {
24 b.Fatal(err)
25 }
26 if len(out) == 0 {
27 b.Fatal("expected renderer output")
28 }
29 outBytes += int64(len(out))
30 }
31 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
32}
33
34func BenchmarkRendererFragmentedDamage(b *testing.B) {
35 const (
36 width = 120
37 height = 40
38 )
39
40 baseline := NewFrame(width, height)
41 markBenchmarkFrame(&baseline)
42 frame := NewFrame(width, height)
43 markBenchmarkFrame(&frame)
44
45 // Create 256 overlapping or vertically adjacent text rectangles in reverse
46 // row/column order. Canonical damage planning must sort and merge these into
47 // stable per-row spans before emission.
48 damage := make([]Damage, 0, 256)
49 for row := 15; row >= 0; row-- {
50 for col := 15; col >= 0; col-- {
51 x, y := col*7, row*2
52 damage = append(damage, Damage{Kind: DamageText, X: x, Y: y, Width: 8, Height: 2})
53 for dy := range 2 {
54 for dx := range 8 {
55 frame.Set(x+dx, y+dy, Cell{Rune: 'z', Style: DefaultStyle()})
56 }
57 }
58 }
59 }
60
61 r := New(Capabilities{})
62 b.ReportAllocs()
63 var outBytes int64
64 for b.Loop() {
65 // Each iteration starts with the identical pre-update terminal shadow.
66 r.replaceShadow(baseline)
67 out, err := r.Draw(frame, damage)
68 if err != nil {
69 b.Fatal(err)
70 }
71 if len(out) == 0 {
72 b.Fatal("expected renderer output")
73 }
74 outBytes += int64(len(out))
75 }
76 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
77}
78
79func BenchmarkRendererScrollFastPath(b *testing.B) {
80 frame := NewFrame(120, 40)
81 markBenchmarkFrame(&frame)
82 r := New(Capabilities{})
83 if _, err := r.Draw(frame, []Damage{FullRedraw()}); err != nil {
84 b.Fatal(err)
85 }
86
87 scrolled := NewFrame(120, 40)
88 copy(scrolled.Cells[0:39*120], frame.Cells[120:40*120])
89 for i := 39 * 120; i < 40*120; i++ {
90 scrolled.Cells[i] = BlankCell()
91 }
92 for x, r := range []rune("new output line") {
93 scrolled.Set(x, 39, Cell{Rune: r, Style: DefaultStyle()})
94 }
95 damage := []Damage{
96 {Kind: DamageScrollUp, X: 0, Y: 0, Width: 120, Height: 40, Count: 1},
97 {Kind: DamageText, X: 0, Y: 39, Width: 120, Height: 1, Count: 1},
98 }
99
100 b.ReportAllocs()
101 var outBytes int64
102 for b.Loop() {
103 r.replaceShadow(frame)
104 out, err := r.Draw(scrolled, damage)
105 if err != nil {
106 b.Fatal(err)
107 }
108 if len(out) == 0 {
109 b.Fatal("expected renderer output")
110 }
111 outBytes += int64(len(out))
112 }
113 b.ReportMetric(float64(outBytes)/float64(b.N), "outbytes/op")
114}