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