v0.3.0 cf370a0

damage_test.go RawHistoryFolder

1package vulkan
2
3import (
4	"bytes"
5	"fmt"
6	"image/color"
7	"os"
8	"testing"
9
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13// damagedContent writes px into the content's pool (the client drawing
14// into the same buffer) and records the damage of the new Seq.
15func damagedContent(t *testing.T, f *os.File, base ports.SurfaceContent, seq uint64, px []byte, hist []ports.SeqDamage) ports.SurfaceContent {
16	t.Helper()
17	if _, err := f.WriteAt(px, 0); err != nil {
18		t.Fatal(err)
19	}
20	c := base
21	c.Seq, c.DamageHistory = seq, hist
22	return c
23}
24
25// Partial redraws are pixel-identical to full ones: with both targets
26// ages (A then B, then one render) and with shm double buffers
27// (render, skip, render).
28func TestRendererDamageMatchesFullRedraw(t *testing.T) {
29	for _, tr := range []ports.BufferTransform{0, 1, 6} {
30		t.Run(fmt.Sprintf("transform %d", tr), func(t *testing.T) { damageMatchesFullRedraw(t, tr, 0) })
31	}
32	// A rotated or flipped output composes the scene into its target.
33	for _, out := range []ports.BufferTransform{1, 6} {
34		t.Run(fmt.Sprintf("output transform %d", out), func(t *testing.T) { damageMatchesFullRedraw(t, 0, out) })
35	}
36}
37
38// damageMatchesFullRedraw checks partial frames of a window whose buffer
39// carries transform tr against full redraws, on an output with transform
40// out (its scene is 64×48; a rotated output's target is 48×64).
41func damageMatchesFullRedraw(t *testing.T, tr, out ports.BufferTransform) {
42	const w, h = 32, 32
43	tw, th := out.Size(64, 48)
44	newR := func() *Renderer {
45		r, err := New(tw, th)
46		if err != nil {
47			t.Skipf("Vulkan unavailable: %v", err)
48		}
49		t.Cleanup(r.Close)
50		return r
51	}
52	damaged, full := newR(), newR()
53	bufsD, err := damaged.ExportTargets(2, nil)
54	if err != nil {
55		t.Skipf("no exportable targets: %v", err)
56	}
57	for _, b := range bufsD {
58		b.Planes[0].File.Close()
59	}
60	px := func(c color.RGBA, x0, y0, x1, y1 int, into []byte) []byte {
61		for y := y0; y < y1; y++ {
62			for x := x0; x < x1; x++ {
63				copy(into[(y*w+x)*4:], []byte{c.B, c.G, c.R, 255})
64			}
65		}
66		return into
67	}
68	pixels := px(color.RGBA{10, 20, 30, 255}, 0, 0, w, h, make([]byte, w*h*4))
69	base := shmContent(t, w, h, w*4, pixels)
70	base.ID, base.Transform = 1, tr
71	f := base.SHM.File
72	scene := ports.Scene{Seq: 5, Transform: out, OutputWidth: 64, OutputHeight: 48, Scale: 1, Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 4, Y: 4, W: w, H: h}}}}
73	hist := []ports.SeqDamage{{Seq: 1, Full: true}}
74	c := damagedContent(t, f, *base, 1, pixels, hist)
75	step := func(i int, c ports.SurfaceContent) {
76		t.Helper()
77		damaged.UseTarget(i % 2)
78		contents := map[ports.WindowID]ports.SurfaceContent{1: c}
79		if err := render(damaged, scene, contents); err != nil {
80			t.Fatal(err)
81		}
82		if err := render(full, ports.Scene{Transform: out, OutputWidth: 64, OutputHeight: 48, Scale: 1, Background: scene.Background, Windows: scene.Windows}, contents); err != nil {
83			t.Fatal(err)
84		}
85		if a, b := readPixels(t, damaged), readPixels(t, full); !bytes.Equal(a.Pix, b.Pix) {
86			t.Fatalf("frame %d differs from a full redraw", i)
87		}
88	}
89	step(0, c)
90	step(1, c) // target 1 drawn whole once
91	before := damaged.redrawn
92	// A: a red square; B: a green one elsewhere. Target 0 holds Seq 1,
93	// so its next frame redraws the union of both.
94	for i, d := range []struct {
95		col  color.RGBA
96		rect ports.Rect
97	}{{color.RGBA{255, 0, 0, 255}, ports.Rect{X: 2, Y: 2, W: 4, H: 4}}, {color.RGBA{0, 255, 0, 255}, ports.Rect{X: 20, Y: 10, W: 3, H: 5}}} {
98		seq := uint64(2 + i)
99		px(d.col, d.rect.X, d.rect.Y, d.rect.X+d.rect.W, d.rect.Y+d.rect.H, pixels)
100		hist = append(hist, ports.SeqDamage{Seq: seq, Rects: []ports.Rect{d.rect}})
101		c = damagedContent(t, f, *base, seq, pixels, hist)
102	}
103	step(2, c)
104	if got := damaged.redrawn - before; tr == 0 && got >= 64*48 {
105		t.Fatalf("partial frame redrew %d pixels", got)
106	}
107	// Render, skip (no frame), render: target 1 is two contents behind.
108	px(color.RGBA{0, 0, 255, 255}, 0, 30, 2, 32, pixels)
109	hist = append(hist, ports.SeqDamage{Seq: 4, Rects: []ports.Rect{{Y: 30, W: 2, H: 2}}})
110	c = damagedContent(t, f, *base, 4, pixels, hist)
111	step(3, c)
112	step(4, c)
113}
114
115// A window left out of a frame (on an overlay plane) and drawn again in
116// the same scene is redrawn whole; one held but no longer drawn is
117// cleared.
118func TestRendererDamageWindowLeftOutAndBack(t *testing.T) {
119	newR := func() *Renderer {
120		r, err := New(64, 48)
121		if err != nil {
122			t.Skipf("Vulkan unavailable: %v", err)
123		}
124		t.Cleanup(r.Close)
125		return r
126	}
127	damaged, full := newR(), newR()
128	c := solidContent(t, 16, 16, color.RGBA{200, 10, 10, 255})
129	c.ID, c.Seq = 1, 1
130	contents := map[ports.WindowID]ports.SurfaceContent{1: c}
131	with := ports.Scene{Seq: 9, Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 4, Y: 4, W: 16, H: 16}}}}
132	without := with
133	without.Windows = nil
134	check := func(s ports.Scene, what string) {
135		t.Helper()
136		if err := render(damaged, s, contents); err != nil {
137			t.Fatal(err)
138		}
139		plain := s
140		plain.Seq = 0
141		if err := render(full, plain, contents); err != nil {
142			t.Fatal(err)
143		}
144		if !bytes.Equal(readPixels(t, damaged).Pix, readPixels(t, full).Pix) {
145			t.Fatalf("%s differs from a full redraw", what)
146		}
147	}
148	check(without, "overlay frame")
149	check(with, "overlay refused, window composed")
150	check(without, "window on the overlay again")
151}

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 25

Tags 10

↑↓ moveenter openesc close