1package vulkan
2
3import (
4 "image/color"
5 "math"
6 "testing"
7
8 "github.com/bnema/neferwl/internal/ports"
9 vk "github.com/bnema/purego-vulkan/vulkan"
10)
11
12// Frames drawn into exported targets reach their dmabufs: another renderer
13// importing a target (as KMS scans it out) sees the frame, and readback
14// reads the target drawn last.
15func TestExportTargets(t *testing.T) {
16 r, err := New(64, 32)
17 if err != nil {
18 t.Skipf("Vulkan unavailable: %v", err)
19 }
20 defer r.Close()
21 bufs, err := r.ExportTargets(2, nil)
22 if err != nil {
23 t.Skipf("no exportable targets: %v", err)
24 }
25 defer func() {
26 for _, b := range bufs {
27 b.Planes[0].File.Close()
28 }
29 }()
30 if len(bufs) != 2 || bufs[0].Width != 64 || bufs[0].Format != fourccXRGB || bufs[0].Planes[0].Stride < 64*4 {
31 t.Fatalf("targets %+v", bufs)
32 }
33 colors := []string{"#ff0000", "#0000ff"}
34 for i, c := range colors {
35 r.UseTarget(i)
36 if err := render(r, ports.Scene{Background: c}, nil); err != nil {
37 t.Fatal(err)
38 }
39 }
40 if got := readPixels(t, r).At(3, 3); got != (color.RGBA{0, 0, 255, 255}) {
41 t.Fatalf("readback of the last target %v", got)
42 }
43 // Import target 0 as a client buffer, like the display would.
44 viewer, err := New(64, 32)
45 if err != nil {
46 t.Fatal(err)
47 }
48 defer viewer.Close()
49 b := bufs[0]
50 b.ID = 1
51 importable := false
52 for _, f := range viewer.DMABuf().Formats {
53 importable = importable || f.Format == b.Format && f.Modifier == b.Modifier
54 }
55 if !importable {
56 t.Skipf("modifier %#x not importable", b.Modifier)
57 }
58 scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 32}}}}
59 contents := map[ports.WindowID]ports.SurfaceContent{1: {ID: 1, Width: 64, Height: 32, Opaque: true, DMABuf: &b}}
60 if err := render(viewer, scene, contents); err != nil {
61 t.Fatal(err)
62 }
63 if got := readPixels(t, viewer).At(10, 10); got != (color.RGBA{255, 0, 0, 255}) {
64 t.Fatalf("target 0 as seen by the display %v", got)
65 }
66}
67
68// The exported HDR image carries PQ while Pixels remains the composed SDR
69// image. A test-only transfer-src readback checks the actual shader output.
70func TestHDRExportTarget(t *testing.T) {
71 r, err := New(32, 16)
72 if err != nil {
73 t.Skipf("Vulkan unavailable: %v", err)
74 }
75 defer r.Close()
76 r.SetHDR(203)
77 // Only this GPU test requests transfer-src on an HDR target.
78 r.hdrReadback = true
79 if r.physical == 0 {
80 t.Skip("no exportable GPU")
81 }
82 r.hdrMods = r.probeModifiers(r.physical, vk.FormatA2r10g10b10UnormPack32)
83 if len(r.hdrMods) == 0 {
84 t.Skip("no HDR modifier supporting transfer-src")
85 }
86 bufs, err := r.ExportTargets(1, r.hdrMods)
87 if err != nil {
88 t.Skipf("no HDR-exportable target: %v", err)
89 }
90 defer bufs[0].Planes[0].File.Close()
91 if bufs[0].Format != fourccXR30 {
92 t.Fatalf("format: %#x", bufs[0].Format)
93 }
94
95 // A real second frame with an unchanged scene must retain the internal
96 // image and use a partial-damage load from its actual prior layout.
97 scene := ports.Scene{Seq: 1, Background: "#ffffff"}
98 for _, tc := range []struct {
99 bg string
100 rgb [3]float64
101 }{
102 {"#ffffff", [3]float64{1, 1, 1}},
103 {"#ff0000", [3]float64{1, 0, 0}},
104 {"#0000ff", [3]float64{0, 0, 1}},
105 {"#808080", [3]float64{128.0 / 255, 128.0 / 255, 128.0 / 255}},
106 } {
107 scene.Seq++
108 scene.Background = tc.bg
109 if err := render(r, scene, nil); err != nil {
110 t.Fatal(err)
111 }
112 checkHDRPixel(t, r, tc.rgb)
113 }
114 before := r.redrawn
115 if err := render(r, scene, nil); err != nil {
116 t.Fatal(err)
117 }
118 if r.redrawn-before >= r.width*r.height {
119 t.Fatalf("unchanged HDR frame redrew %d pixels", r.redrawn-before)
120 }
121 checkHDRPixel(t, r, [3]float64{128.0 / 255, 128.0 / 255, 128.0 / 255})
122 if r.hdrOwn.layout != vk.ImageLayoutTransferSrcOptimal {
123 t.Fatalf("internal layout after HDR: %v", r.hdrOwn.layout)
124 }
125 if got := readPixels(t, r).RGBAAt(0, 0); got.R != 128 || got.G != 128 || got.B != 128 {
126 t.Fatalf("SDR readback: %v", got)
127 }
128}
129
130// Test-only target readback: production HDR images do not have transfer-src.
131func checkHDRPixel(t *testing.T, r *Renderer, rgb [3]float64) {
132 t.Helper()
133 v := pqTargetWords(t, r)[0]
134 red, green, blue := hdrPixel(rgb[0], rgb[1], rgb[2], 203)
135 for i, want := range []float64{blue, green, red} {
136 got := float64(v>>uint(i*10)&1023) / 1023
137 if math.Abs(got-want) > 0.005 {
138 t.Fatalf("rgb %v channel %d: PQ %.5f want %.5f", rgb, i, got, want)
139 }
140 }
141}