1package drm
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10func TestStateDelay(t *testing.T) {
11 o, _, _ := testOutput(t) // 60 Hz
12 p := o.nominalPeriod()
13 lead := stateLead(p)
14 last := 10 * time.Second
15 for _, tc := range []struct {
16 name string
17 vrr bool
18 last time.Duration
19 now time.Duration
20 want time.Duration
21 }{
22 {"no vblank yet", false, 0, last, 0},
23 {"right after a vblank", false, last, last + time.Millisecond, p - lead - time.Millisecond},
24 {"inside the lead", false, last, last + p - lead/2, 0},
25 {"periods later", false, last, last + 3*p + time.Millisecond, p - lead - time.Millisecond},
26 {"stale vblank", false, last, last + vblankStale + time.Millisecond, 0},
27 {"VRR", true, last, last + time.Millisecond, 0},
28 } {
29 o.vrrOn, o.lastVblankAt = tc.vrr, tc.last
30 if got := o.stateDelay(tc.now); got != tc.want {
31 t.Errorf("%s: delay %v, want %v", tc.name, got, tc.want)
32 }
33 }
34}
35
36// A cursor move right after a vblank waits: the frame that comes before
37// the deadline carries it, in a single commit.
38func TestDeferredCursorRidesOnFrame(t *testing.T) {
39 o, _, commits := testOutput(t)
40 o.cursor.image = true
41 o.stateAt = time.Now().Add(time.Hour) // the vblank is far
42 o.cursor.Move(7, 7)
43 if err := o.commitState(false); err != nil {
44 t.Fatal(err)
45 }
46 if len(*commits) != 0 || o.stateAt.IsZero() || o.frame.pendingCommit() {
47 t.Fatalf("cursor committed at once: %d commits, due %v", len(*commits), o.stateAt)
48 }
49 if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != nil {
50 t.Fatal(err)
51 }
52 if v, _ := (*commits)[0].req.value(tCursor, 14); v != 7 || !o.stateAt.IsZero() {
53 t.Fatalf("frame cursor x %d, due %v", v, o.stateAt)
54 }
55 if s := o.takeFlipStats(); s.stateCarried != 1 || s.stateCommits != 0 {
56 t.Fatalf("carried %d, state commits %d, want 1, 0", s.stateCarried, s.stateCommits)
57 }
58}
59
60// With no frame, the deferred move commits alone once due, and a late
61// timer does not push it by another period.
62func TestDeferredCursorCommitsWhenDue(t *testing.T) {
63 o, _, commits := testOutput(t)
64 o.cursor.image = true
65 o.stateAt = time.Now().Add(time.Hour)
66 o.cursor.Move(4, 4)
67 if err := o.commitState(false); err != nil || len(*commits) != 0 {
68 t.Fatalf("err %v, %d commits", err, len(*commits))
69 }
70 o.stateAt = time.Now().Add(-time.Millisecond) // the timer fired late
71 if err := o.commitState(false); err != nil || len(*commits) != 1 {
72 t.Fatalf("due commit: %v, %d commits", err, len(*commits))
73 }
74 if v, _ := (*commits)[0].req.value(tCursor, 14); v != 4 || !o.stateAt.IsZero() {
75 t.Fatalf("cursor x %d, due %v", v, o.stateAt)
76 }
77 if s := o.takeFlipStats(); s.stateCommits != 1 || s.stateCarried != 0 {
78 t.Fatalf("state commits %d, carried %d, want 1, 0", s.stateCommits, s.stateCarried)
79 }
80}
81
82// A move undone before its deadline commits nothing and clears the wait.
83func TestDeferredCursorMovedBack(t *testing.T) {
84 o, _, commits := testOutput(t)
85 o.cursor.image = true
86 o.cursor.Move(0, 0)
87 o.cursor.applied = o.cursor.desired()
88 o.stateAt = time.Now().Add(time.Hour)
89 o.cursor.Move(9, 9)
90 if err := o.commitState(false); err != nil || o.stateAt.IsZero() {
91 t.Fatalf("err %v, due %v", err, o.stateAt)
92 }
93 o.cursor.Move(0, 0)
94 if err := o.commitState(false); err != nil || !o.stateAt.IsZero() || len(*commits) != 0 {
95 t.Fatalf("err %v, due %v, %d commits", err, o.stateAt, len(*commits))
96 }
97}
98
99// Powering off drops a deferred state change: an off output has no vblank,
100// and an armed deadline would wake Run forever.
101func TestPowerOffDropsDeferredState(t *testing.T) {
102 o, _, _ := testOutput(t)
103 o.stateAt, o.lastVblankAt = time.Now().Add(time.Hour), time.Second
104 if err := o.powerOff(); err != nil {
105 t.Fatal(err)
106 }
107 if !o.stateAt.IsZero() || o.lastVblankAt != 0 {
108 t.Fatalf("due %v, vblank %v after power off", o.stateAt, o.lastVblankAt)
109 }
110}
111
112// Vsynced completions, state ones included, give the vblank; a tearing
113// flip does not.
114func TestCompletionRecordsVblank(t *testing.T) {
115 o, _, commits := testOutput(t)
116 o.cursor.image = true
117 o.cursor.Move(2, 2)
118 if err := o.commitState(false); err != nil || len(*commits) != 1 {
119 t.Fatalf("err %v, %d commits", err, len(*commits))
120 }
121 ev := eventOf((*commits)[0])
122 ev.when = 5 * time.Second
123 if !o.completed(ev, map[ports.WindowID]uint64{}) || o.lastVblankAt != ev.when {
124 t.Fatalf("vblank %v, want %v", o.lastVblankAt, ev.when)
125 }
126 o.noteVblank(pendingFrame{async: true}, 6*time.Second)
127 if o.lastVblankAt != ev.when {
128 t.Fatalf("tearing flip taken as a vblank: %v", o.lastVblankAt)
129 }
130}