1package vulkan
2
3import (
4 "encoding/binary"
5 "image/color"
6 "math"
7 "os"
8 "slices"
9 "testing"
10 "unsafe"
11
12 "github.com/bnema/neferwl/internal/ports"
13 "golang.org/x/sys/unix"
14)
15
16func TestExtendedLinearClient(t *testing.T) {
17 for _, hdr := range []bool{false, true} {
18 t.Run(map[bool]string{false: "SDR", true: "HDR"}[hdr], func(t *testing.T) {
19 var r *Renderer
20 if hdr {
21 r = hdrTestRenderer(t)
22 } else {
23 var err error
24 r, err = New(64, 16)
25 if err != nil {
26 t.Skipf("Vulkan unavailable: %v", err)
27 }
28 defer r.Close()
29 }
30 format := ports.DMABufFormat{Format: fourcc('A', 'B', '4', 'H'), Modifier: 0}
31 if !slices.Contains(r.DMABuf().Formats, format) {
32 t.Skip("ABGR16161616F linear import unavailable")
33 }
34 // 1.0 = 80 nits, 12.5 = 1000 nits, -0.25 is out of gamut.
35 pixels := make([]byte, 64*16*8)
36 for y := 0; y < 16; y++ {
37 for x := 0; x < 64; x++ {
38 v := uint16(0x3c00)
39 if x >= 20 && x < 40 {
40 v = 0x4a40
41 }
42 if x >= 40 {
43 v = 0xb400
44 }
45 i := (y*64 + x) * 8
46 for ch := 0; ch < 3; ch++ {
47 binary.LittleEndian.PutUint16(pixels[i+ch*2:], v)
48 }
49 binary.LittleEndian.PutUint16(pixels[i+6:], 0x3c00)
50 }
51 }
52 mem, err := unix.MemfdCreate("fp16-client", unix.MFD_ALLOW_SEALING)
53 if err != nil {
54 t.Fatal(err)
55 }
56 defer unix.Close(mem)
57 if err := unix.Ftruncate(mem, int64(len(pixels))); err != nil {
58 t.Fatal(err)
59 }
60 if _, err := unix.Pwrite(mem, pixels, 0); err != nil {
61 t.Fatal(err)
62 }
63 if _, err := unix.FcntlInt(uintptr(mem), unix.F_ADD_SEALS, unix.F_SEAL_SHRINK); err != nil {
64 t.Fatal(err)
65 }
66 dev, err := os.OpenFile("/dev/udmabuf", os.O_RDWR, 0)
67 if err != nil {
68 t.Skipf("udmabuf unavailable: %v", err)
69 }
70 defer dev.Close()
71 arg := struct {
72 memfd, flags uint32
73 offset, size uint64
74 }{memfd: uint32(mem), flags: 1, size: uint64(len(pixels))}
75 fd, _, errno := unix.Syscall(unix.SYS_IOCTL, dev.Fd(), 0x40187542, uintptr(unsafe.Pointer(&arg)))
76 if errno != 0 {
77 t.Skipf("udmabuf create: %v", errno)
78 }
79 f := os.NewFile(fd, "fp16-client")
80 defer f.Close()
81 buf := &ports.DMABuf{ID: 164, Width: 64, Height: 16, Format: format.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 512}}}
82 c := ports.SurfaceContent{ID: 1, Width: 64, Height: 16, Opaque: true, DMABuf: buf, Color: ports.SurfaceColor{TF: ports.ColorTFExtendedLinear, Primaries: ports.ColorPrimariesSRGB}}
83 scene := ports.Scene{Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 16}}}}
84 if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: c}); err != nil {
85 t.Fatal(err)
86 }
87 if hdr {
88 for _, tc := range []struct {
89 x int
90 nits float64
91 }{{5, 80}, {25, 1000}, {45, 0}} {
92 got := hdrTargetAt(t, r, tc.x, 8)
93 want := pqEncode(tc.nits)
94 for i, v := range got {
95 if math.Abs(v-want) > .009 {
96 t.Errorf("x=%d channel=%d PQ=%.4f want %.4f", tc.x, i, v, want)
97 }
98 }
99 }
100 } else {
101 for _, tc := range []struct {
102 x int
103 v byte
104 }{{5, 168}, {25, 255}, {45, 0}} {
105 got := readPixels(t, r).RGBAAt(tc.x, 8)
106 if !near(got, color.RGBA{tc.v, tc.v, tc.v, 255}, 3) {
107 t.Errorf("x=%d got %v want %d", tc.x, got, tc.v)
108 }
109 }
110 }
111 })
112 }
113}