pq_client_test.go RawHistoryFolder

1package vulkan
2
3import (
4	"image/color"
5	"math"
6	"slices"
7	"testing"
8
9	"github.com/bnema/neferwl/internal/ports"
10)
11
12// A PQ 2101010 client is decoded before the SDR intermediate, which clips
13// highlights above reference white. The GPU and udmabuf are optional.
14func TestPQClientComposition(t *testing.T) {
15	r, err := New(64, 4)
16	if err != nil {
17		t.Skipf("Vulkan unavailable: %v", err)
18	}
19	defer r.Close()
20	type sample struct {
21		rgb   [3]float64
22		alpha float64
23	}
24	// Neutral values test absolute luminance; saturated colors exercise the
25	// BT.2020 -> BT.709 matrix (and the clipping of out-of-gamut channels).
26	samples := []sample{
27		{[3]float64{100, 100, 100}, 1}, {[3]float64{203, 203, 203}, 1}, {[3]float64{1000, 1000, 1000}, 1},
28		{[3]float64{100, 0, 0}, 1}, {[3]float64{0, 0, 100}, 1}, {[3]float64{0, 100, 0}, .6666667},
29	}
30	for _, name := range [][4]byte{{'X', 'R', '3', '0'}, {'X', 'B', '3', '0'}, {'A', 'B', '3', '0'}} {
31		t.Run(string(name[:]), func(t *testing.T) {
32			format := ports.DMABufFormat{Format: fourcc(name[0], name[1], name[2], name[3]), Modifier: 0}
33			if !slices.Contains(r.DMABuf().Formats, format) {
34				t.Skipf("linear %s unavailable", name)
35			}
36			// Store premultiplied PQ codes for alpha buffers. Alpha uses two bits.
37			f := udmabuf(t, 64, 4, func(x, _ int) [4]byte {
38				s := samples[min(x/10, len(samples)-1)]
39				a := uint32(math.Round(s.alpha * 3))
40				if name[0] == 'X' {
41					a = 0 // X bits are undefined, and must be ignored.
42					s.alpha = 1
43				}
44				var ch [3]uint32
45				for i, n := range s.rgb {
46					ch[i] = uint32(math.Round(pqEncode(n) * s.alpha * 1023))
47				}
48				var packed uint32
49				if name[1] == 'R' {
50					packed = ch[2] | ch[1]<<10 | ch[0]<<20
51				} else {
52					packed = ch[0] | ch[1]<<10 | ch[2]<<20
53				}
54				packed |= a << 30
55				return [4]byte{byte(packed), byte(packed >> 8), byte(packed >> 16), byte(packed >> 24)}
56			})
57			buf := &ports.DMABuf{ID: uint64(format.Format), Width: 64, Height: 4, Format: format.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}}
58			c := ports.SurfaceContent{ID: 1, Width: 64, Height: 4, Opaque: name[0] == 'X', Color: ports.SurfaceColor{TF: ports.ColorTFPQ, Primaries: ports.ColorPrimariesBT2020}, DMABuf: buf}
59			scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 4}}}}
60			if err := render(r, scene, map[ports.WindowID]ports.SurfaceContent{1: c}); err != nil {
61				t.Fatal(err)
62			}
63			for i, s := range samples {
64				alpha := s.alpha
65				if name[0] == 'X' {
66					alpha = 1
67				}
68				var nits [3]float64
69				for j, n := range s.rgb {
70					sourceAlpha := s.alpha
71					if name[0] == 'X' {
72						sourceAlpha = 1
73					}
74					code := math.Round(pqEncode(n)*sourceAlpha*1023) / 1023
75					p := math.Pow(code/sourceAlpha, 32.0/2523)
76					nits[j] = math.Pow(math.Max(p-3424.0/4096, 0)/(2413.0/128-p*2392.0/128), 16384.0/2610) * 10000
77				}
78				linear := [3]float64{
79					1.660491*nits[0] - 0.587641*nits[1] - 0.072850*nits[2],
80					-0.124550*nits[0] + 1.132900*nits[1] - 0.008349*nits[2],
81					-0.018151*nits[0] - 0.100579*nits[1] + 1.118730*nits[2],
82				}
83				want := color.RGBA{A: 255}
84				channels := []*uint8{&want.R, &want.G, &want.B}
85				for j, n := range linear {
86					v := math.Max(0, math.Min(n/203, 1))
87					if v <= .0031308 {
88						v *= 12.92
89					} else {
90						v = 1.055*math.Pow(v, 1/2.4) - .055
91					}
92					// Composition blends premultiplied source onto a black opaque fill.
93					*channels[j] = uint8(math.Round(v * alpha * 255))
94				}
95				got := readPixels(t, r).RGBAAt(i*10+5, 2)
96				if !near(got, want, 4) {
97					t.Errorf("sample %d: got %v want %v", i, got, want)
98				}
99			}
100		})
101	}
102}

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 23

Tags 10

↑↓ moveenter openesc close