1package gpu
2
3import (
4 "os"
5 "syscall"
6 "testing"
7
8 "github.com/bnema/neferbar/internal/glyph"
9 "github.com/bnema/neferbar/internal/racecheck"
10 "github.com/bnema/neferbar/internal/syncobj"
11)
12
13// newTestRenderer needs a Vulkan device with DMA-BUF export; it skips without one.
14func newTestRenderer(t *testing.T) (*Renderer, *Device, *syncobj.Node) {
15 t.Helper()
16 var st syscall.Stat_t
17 if err := syscall.Stat("/dev/dri/renderD128", &st); err != nil {
18 t.Skip("no render node")
19 }
20 dev, err := Open(uint64(st.Rdev))
21 if err != nil {
22 t.Skipf("no matching Vulkan device: %v", err)
23 }
24 t.Cleanup(dev.Close)
25 node, err := syncobj.Open("/dev/dri/renderD128")
26 if err != nil {
27 t.Skip(err)
28 }
29 t.Cleanup(func() { node.Close() })
30 mods, err := dev.ExportableModifiers()
31 if err != nil || len(mods) == 0 {
32 t.Skip("no exportable modifier")
33 }
34 var mod Modifier
35 for m := range mods {
36 mod = Modifier(m)
37 break
38 }
39 paths, err := glyph.FindFonts("monospace")
40 if err != nil {
41 t.Skip(err)
42 }
43 if _, err := os.Stat(paths[0]); err != nil {
44 t.Skip(err)
45 }
46 face, err := glyph.Load(paths, 16)
47 if err != nil {
48 t.Skip(err)
49 }
50 t.Cleanup(face.Close)
51 r, err := NewRenderer(dev, node, mod, face, 800, int32(face.CellH))
52 if err != nil {
53 t.Fatal(err)
54 }
55 t.Cleanup(r.Close)
56 return r, dev, node
57}
58
59// TestDrawSteadyStateAllocs reports the allocations of one Draw once the
60// atlas is warm: the budget is what the Vulkan bindings force per call.
61func TestDrawSteadyStateAllocs(t *testing.T) {
62 if racecheck.Enabled {
63 t.Skip("the race detector allocates")
64 }
65 r, _, node := newTestRenderer(t)
66 cells := make([]Cell, r.Cols())
67 for i := range cells {
68 cells[i] = Cell{Rune: rune('a' + i%26), FG: [3]uint8{255, 255, 255}, BG: [3]uint8{0, 0, 40}}
69 }
70 var f Frame
71 step := func() {
72 ok, err := r.Draw(cells, [3]uint8{}, &f)
73 if err != nil || !ok {
74 t.Fatalf("draw ok=%v err=%v", ok, err)
75 }
76 // Play the compositor: signal the release point, then hand it back.
77 s := &r.slots[f.Slot]
78 if err := node.Signal(s.releaseHandle, s.releasePoint); err != nil {
79 t.Fatal(err)
80 }
81 if err := r.Released(f.Slot); err != nil {
82 t.Fatal(err)
83 }
84 }
85 for i := 0; i < 8; i++ { // warm: atlas upload, lazily created state
86 step()
87 }
88 got := testing.AllocsPerRun(200, step)
89 t.Logf("allocations per Draw+Released: %.1f", got)
90 if got > 0 {
91 t.Errorf("steady-state Draw allocates %.1f objects; want 0", got)
92 }
93}