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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 393 | 393 | } | |
| 394 | 394 | ||
| 395 | 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 | 397 | px := make([]byte, w*h*4) | |
| 398 | 398 | for i := 0; i < len(px); i += 4 { | |
| 399 | 399 | 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 | 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 | 406 | t.Helper() | |
| 407 | 407 | fd, err := unix.MemfdCreate("shm-test", unix.MFD_CLOEXEC) | |
| 408 | 408 | 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, 255 | 399 | 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 | + | } | |||