1//go:build linux
2
3package syncobj_test
4
5import (
6 "os"
7 "testing"
8
9 "github.com/bnema/nefergui/internal/presentation/syncobj"
10 "github.com/bnema/nefergui/internal/presentation/vkdevice"
11 "golang.org/x/sys/unix"
12)
13
14// This test proves that the selected render node accepts an actual Vulkan
15// SYNC_FD, transfers a point into a DRM timeline, and exports a release point
16// as a SYNC_FD that Vulkan can temporarily import. It is not an end-to-end
17// compositor acceptance test; the Wayland commit gate is separate.
18func TestVulkanDRMBridge(t *testing.T) {
19 path := os.Getenv("NEFERGUI_RENDER_NODE")
20 if path == "" {
21 t.Skip("set NEFERGUI_RENDER_NODE to the feedback-matched render node")
22 }
23 var stat unix.Stat_t
24 if err := unix.Stat(path, &stat); err != nil {
25 t.Fatal(err)
26 }
27 dev, err := vkdevice.Open(vkdevice.DeviceIdentity(uint64(stat.Rdev)))
28 if err != nil {
29 t.Fatal(err)
30 }
31 defer dev.Close()
32 node, err := syncobj.Open(path)
33 if err != nil {
34 t.Fatal(err)
35 }
36 defer node.Close()
37 acquire, err := node.Create()
38 if err != nil {
39 t.Fatal(err)
40 }
41 defer node.Destroy(acquire)
42 release, err := node.Create()
43 if err != nil {
44 t.Fatal(err)
45 }
46 defer node.Destroy(release)
47 out, err := dev.NewBinaryFence(true)
48 if err != nil {
49 t.Fatal(err)
50 }
51 defer out.Close()
52 if err = dev.SubmitEmpty(out, nil); err != nil {
53 t.Fatal(err)
54 }
55 fd, err := out.Export()
56 if err != nil {
57 t.Fatal(err)
58 }
59 // Do not close fd on a successful AcquirePoint (the ioctl wrapper closes
60 // after import). On an ioctl failure it remains ours.
61 if err = node.AcquirePoint(fd, acquire, 1); err != nil {
62 _ = unix.Close(fd)
63 t.Fatalf("Vulkan SYNC_FD → FD_TO_HANDLE → TRANSFER acquire: %v", err)
64 }
65 ready, err := node.Signaled(acquire, 1)
66 if err != nil || !ready {
67 t.Fatalf("acquire TIMELINE_WAIT ready=%v err=%v", ready, err)
68 }
69 if err = node.Transfer(acquire, 1, release, 2); err != nil {
70 t.Fatalf("transfer acquire to release: %v", err)
71 }
72 ready, err = node.Signaled(release, 2)
73 if err != nil || !ready {
74 t.Fatalf("release TIMELINE_WAIT ready=%v err=%v", ready, err)
75 }
76 value, err := node.Query(release)
77 if err != nil || value < 2 {
78 t.Fatalf("QUERY release point=%d err=%v", value, err)
79 }
80 back, err := node.ReleaseFence(release, 2)
81 if err != nil {
82 t.Fatalf("TRANSFER and HANDLE_TO_FD EXPORT_SYNC_FILE: %v", err)
83 }
84 wait, err := dev.NewBinaryFence(false)
85 if err != nil {
86 _ = unix.Close(back)
87 t.Fatal(err)
88 }
89 defer wait.Close()
90 if err = wait.ImportTemporary(back); err != nil {
91 _ = unix.Close(back)
92 t.Fatalf("Vulkan temporary SYNC_FD import: %v", err)
93 }
94 if err = dev.SubmitEmpty(nil, wait); err != nil {
95 t.Fatalf("Vulkan release-fence wait: %v", err)
96 }
97 t.Logf("render node %s: CREATE acquire=%d release=%d; Vulkan export SYNC_FD → FD_TO_HANDLE IMPORT_SYNC_FILE → TRANSFER(1) → TIMELINE_WAIT → TRANSFER(2) → QUERY=%d → HANDLE_TO_FD EXPORT_SYNC_FILE → Vulkan temporary import/wait: accepted", path, acquire, release, value)
98}