detached cd27fce

test(vulkan): benchmark a full compose into each exported target modifier

commit
cd27fce0053c64b809419b8b48a8b595706ce3e3
author
brice · · 2026-10-05 04:19 UTC
parent
4ad7ed1
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +85 -2
M internal/adapters/vulkan/renderer_test.go +2 -2
@@ -393,7 +393,7 @@ func TestRendererPreviewSmooth(t *testing.T) {
393393 }
394394
395395 // solidContent is a w×h B8G8R8A8 buffer of one color.
396-func solidContent(t *testing.T, w, h int, c color.RGBA) ports.SurfaceContent {
396+func solidContent(t testing.TB, w, h int, c color.RGBA) ports.SurfaceContent {
397397 px := make([]byte, w*h*4)
398398 for i := 0; i < len(px); i += 4 {
399399 px[i], px[i+1], px[i+2], px[i+3] = c.B, c.G, c.R, 255
@@ -402,7 +402,7 @@ func solidContent(t *testing.T, w, h int, c color.RGBA) ports.SurfaceContent {
402402 }
403403
404404 // shmContent puts pixels in a memfd pool, like a wl_shm client.
405-func shmContent(t *testing.T, w, h, stride int, pixels []byte) *ports.SurfaceContent {
405+func shmContent(t testing.TB, w, h, stride int, pixels []byte) *ports.SurfaceContent {
406406 t.Helper()
407407 fd, err := unix.MemfdCreate("shm-test", unix.MFD_CLOEXEC)
408408 if err != nil {
@@ -393,7 +393,7 @@ func TestRendererPreviewSmooth(t *testing.T) {
393 }393 }
394 394
395 // solidContent is a w×h B8G8R8A8 buffer of one color.395 // solidContent is a w×h B8G8R8A8 buffer of one color.
396-func solidContent(t *testing.T, w, h int, c color.RGBA) ports.SurfaceContent {396+func solidContent(t testing.TB, w, h int, c color.RGBA) ports.SurfaceContent {
397 px := make([]byte, w*h*4)397 px := make([]byte, w*h*4)
398 for i := 0; i < len(px); i += 4 {398 for i := 0; i < len(px); i += 4 {
399 px[i], px[i+1], px[i+2], px[i+3] = c.B, c.G, c.R, 255399 px[i], px[i+1], px[i+2], px[i+3] = c.B, c.G, c.R, 255
@@ -402,7 +402,7 @@ func solidContent(t *testing.T, w, h int, c color.RGBA) ports.SurfaceContent {
402 }402 }
403 403
404 // shmContent puts pixels in a memfd pool, like a wl_shm client.404 // shmContent puts pixels in a memfd pool, like a wl_shm client.
405-func shmContent(t *testing.T, w, h, stride int, pixels []byte) *ports.SurfaceContent {405+func shmContent(t testing.TB, w, h, stride int, pixels []byte) *ports.SurfaceContent {
406 t.Helper()406 t.Helper()
407 fd, err := unix.MemfdCreate("shm-test", unix.MFD_CLOEXEC)407 fd, err := unix.MemfdCreate("shm-test", unix.MFD_CLOEXEC)
408 if err != nil {408 if err != nil {
A internal/adapters/vulkan/targets_perf_test.go +83 -0
@@ -0,0 +1,83 @@
1+package vulkan
2+
3+import (
4+ "fmt"
5+ "image/color"
6+ "testing"
7+ "time"
8+
9+ "github.com/bnema/neferwl/internal/ports"
10+)
11+
12+// BenchmarkComposeFullTarget composes a full-redraw frame (three windows
13+// covering the output, moved every frame as a workspace swipe does) into an
14+// exported target of each modifier the device offers for B8G8R8A8, and waits
15+// for the GPU after every frame: the time per frame is the GPU cost of the
16+// target's tiling/compression layout (DCC retile passes included). Select the
17+// GPU with NEFERWL_VK_DEVICE; the size is 2560x1600.
18+func BenchmarkComposeFullTarget(b *testing.B) {
19+ const w, h = 2560, 1600
20+ probe, err := New(w, h)
21+ if err != nil {
22+ b.Skipf("Vulkan unavailable: %v", err)
23+ }
24+ mods := append([]uint64(nil), probe.renderMods...)
25+ planes := probe.modPlanes
26+ probe.Close()
27+ if len(mods) == 0 {
28+ b.Skip("device exports no render modifier")
29+ }
30+ for _, m := range mods {
31+ b.Run(fmt.Sprintf("%#x/planes=%d", m, planes[m]), func(b *testing.B) {
32+ r, err := New(w, h)
33+ if err != nil {
34+ b.Skipf("Vulkan unavailable: %v", err)
35+ }
36+ defer r.Close()
37+ bufs, err := r.ExportTargets(2, []uint64{m}, false)
38+ if err != nil {
39+ b.Skipf("modifier %#x not exportable: %v", m, err)
40+ }
41+ defer func() {
42+ for _, buf := range bufs {
43+ for _, p := range buf.Planes {
44+ p.File.Close()
45+ }
46+ }
47+ }()
48+ ww := w / 2
49+ contents := map[ports.WindowID]ports.SurfaceContent{}
50+ for i, c := range []color.RGBA{{R: 200, A: 255}, {G: 200, A: 255}, {B: 200, A: 255}} {
51+ content := solidContent(b, ww, h, c)
52+ content.ID, content.Surface, content.Seq, content.Version = ports.WindowID(i+1), uint64(i+1), 1, 1
53+ contents[content.ID] = content
54+ }
55+ scene := func(i int) ports.Scene {
56+ x := i % 64
57+ return ports.Scene{Seq: uint64(i + 1), Scale: 1, Background: "#101010", Windows: []ports.SceneWindow{
58+ {ID: 1, Rect: ports.Rect{X: x - ww, W: ww, H: h}},
59+ {ID: 2, Rect: ports.Rect{X: x, W: ww, H: h}},
60+ {ID: 3, Rect: ports.Rect{X: x + ww, W: ww, H: h}},
61+ }}
62+ }
63+ frame := func(i int) {
64+ r.UseTarget(i % 2)
65+ if err := render(r, scene(i), contents); err != nil {
66+ b.Fatal(err)
67+ }
68+ if err := r.waitFrame(r.submitted); err != nil {
69+ b.Fatal(err)
70+ }
71+ }
72+ for i := range 8 {
73+ frame(i)
74+ }
75+ b.ResetTimer()
76+ start := time.Now()
77+ for i := range b.N {
78+ frame(i + 8)
79+ }
80+ b.ReportMetric(float64(time.Since(start).Microseconds())/1000/float64(b.N), "ms/frame")
81+ })
82+ }
83+}
@@ -0,0 +1,83 @@
1+package vulkan
2+
3+import (
4+ "fmt"
5+ "image/color"
6+ "testing"
7+ "time"
8+
9+ "github.com/bnema/neferwl/internal/ports"
10+)
11+
12+// BenchmarkComposeFullTarget composes a full-redraw frame (three windows
13+// covering the output, moved every frame as a workspace swipe does) into an
14+// exported target of each modifier the device offers for B8G8R8A8, and waits
15+// for the GPU after every frame: the time per frame is the GPU cost of the
16+// target's tiling/compression layout (DCC retile passes included). Select the
17+// GPU with NEFERWL_VK_DEVICE; the size is 2560x1600.
18+func BenchmarkComposeFullTarget(b *testing.B) {
19+ const w, h = 2560, 1600
20+ probe, err := New(w, h)
21+ if err != nil {
22+ b.Skipf("Vulkan unavailable: %v", err)
23+ }
24+ mods := append([]uint64(nil), probe.renderMods...)
25+ planes := probe.modPlanes
26+ probe.Close()
27+ if len(mods) == 0 {
28+ b.Skip("device exports no render modifier")
29+ }
30+ for _, m := range mods {
31+ b.Run(fmt.Sprintf("%#x/planes=%d", m, planes[m]), func(b *testing.B) {
32+ r, err := New(w, h)
33+ if err != nil {
34+ b.Skipf("Vulkan unavailable: %v", err)
35+ }
36+ defer r.Close()
37+ bufs, err := r.ExportTargets(2, []uint64{m}, false)
38+ if err != nil {
39+ b.Skipf("modifier %#x not exportable: %v", m, err)
40+ }
41+ defer func() {
42+ for _, buf := range bufs {
43+ for _, p := range buf.Planes {
44+ p.File.Close()
45+ }
46+ }
47+ }()
48+ ww := w / 2
49+ contents := map[ports.WindowID]ports.SurfaceContent{}
50+ for i, c := range []color.RGBA{{R: 200, A: 255}, {G: 200, A: 255}, {B: 200, A: 255}} {
51+ content := solidContent(b, ww, h, c)
52+ content.ID, content.Surface, content.Seq, content.Version = ports.WindowID(i+1), uint64(i+1), 1, 1
53+ contents[content.ID] = content
54+ }
55+ scene := func(i int) ports.Scene {
56+ x := i % 64
57+ return ports.Scene{Seq: uint64(i + 1), Scale: 1, Background: "#101010", Windows: []ports.SceneWindow{
58+ {ID: 1, Rect: ports.Rect{X: x - ww, W: ww, H: h}},
59+ {ID: 2, Rect: ports.Rect{X: x, W: ww, H: h}},
60+ {ID: 3, Rect: ports.Rect{X: x + ww, W: ww, H: h}},
61+ }}
62+ }
63+ frame := func(i int) {
64+ r.UseTarget(i % 2)
65+ if err := render(r, scene(i), contents); err != nil {
66+ b.Fatal(err)
67+ }
68+ if err := r.waitFrame(r.submitted); err != nil {
69+ b.Fatal(err)
70+ }
71+ }
72+ for i := range 8 {
73+ frame(i)
74+ }
75+ b.ResetTimer()
76+ start := time.Now()
77+ for i := range b.N {
78+ frame(i + 8)
79+ }
80+ b.ReportMetric(float64(time.Since(start).Microseconds())/1000/float64(b.N), "ms/frame")
81+ })
82+ }
83+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 26

Tags 10

↑↓ moveenter openesc close