vrr_flip_gap_test.go RawHistoryFolder

1package drm
2
3import (
4	"context"
5	"os"
6	"testing"
7	"time"
8
9	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
10	"github.com/bnema/neferwl/internal/ports"
11	"github.com/stretchr/testify/mock"
12)
13
14// The gap starts only after a game frame's flip under VRR.
15func TestStartFlipGap(t *testing.T) {
16	for _, tc := range []struct {
17		name             string
18		gap              time.Duration
19		frame, vrr, game bool
20		want             bool
21	}{
22		{"game frame under VRR", time.Millisecond, true, true, true, true},
23		{"gap off", 0, true, true, true, false},
24		{"state commit keeps the gap", time.Millisecond, false, true, true, true},
25		{"state commit keeps no gap", time.Millisecond, false, false, false, false},
26		{"VRR off", time.Millisecond, true, false, true, false},
27		{"desktop under VRR hold", time.Millisecond, true, true, false, false},
28	} {
29		// A gap is pending from the last game frame when it applies.
30		pending := time.Time{}
31		if tc.vrr && tc.game {
32			pending = time.Now().Add(time.Hour)
33		}
34		o := &Output{vrrFlipGap: tc.gap, vrrOn: tc.vrr, vrrGame: tc.game, flipGapUntil: pending}
35		o.startFlipGap(pendingFrame{frame: tc.frame})
36		if got := !o.flipGapUntil.IsZero(); got != tc.want {
37			t.Errorf("%s: gap started %v, want %v", tc.name, got, tc.want)
38		}
39	}
40}
41
42// A cursor commit that completes inside the gap does not end it.
43func TestStateCommitKeepsFlipGap(t *testing.T) {
44	o, _, commits := testOutput(t)
45	o.vrrFlipGap, o.vrrOn, o.vrrGame = time.Hour, true, true
46	seen := map[ports.WindowID]uint64{}
47	if err := o.commitFrame(70, nil, false, true, pendingFrame{}); err != nil {
48		t.Fatal(err)
49	}
50	o.completed(eventOf((*commits)[0]), seen)
51	gap := o.flipGapUntil
52	if gap.IsZero() {
53		t.Fatal("no gap after a game frame")
54	}
55	o.cursor.image = true
56	o.lastFrame = time.Time{} // the cursor commits alone
57	o.cursor.Move(3, 3)
58	if err := o.commitState(o.stateVRR()); err != nil || len(*commits) != 2 {
59		t.Fatalf("cursor commit: %v, %d commits", err, len(*commits))
60	}
61	o.completed(eventOf((*commits)[1]), seen)
62	if o.flipGapUntil != gap {
63		t.Fatalf("gap %v after a cursor commit, want %v", o.flipGapUntil, gap)
64	}
65}
66
67// A modeset drops a pending gap: it belonged to the old display state.
68func TestModesetClearsFlipGap(t *testing.T) {
69	o, k, _ := testOutput(t)
70	o.flipGapUntil = time.Now().Add(time.Hour)
71	k.EXPECT().createBlob(mock.Anything).Return(99, nil).Once()
72	if err := o.modeset(); err != nil {
73		t.Fatal(err)
74	}
75	if !o.flipGapUntil.IsZero() {
76		t.Fatalf("gap until %v after modeset", o.flipGapUntil)
77	}
78}
79
80// Run commits the next game frame no sooner than the gap after the flip
81// event, then commits it on its own when the gap ends.
82func TestRunWaitsVRRFlipGap(t *testing.T) {
83	o, k, commits, commitMu := testOutputMu(t)
84	o.cursor, o.tearing = nil, false
85	o.vrrFlipGap = 30 * time.Millisecond
86	flips := make(chan flipEvent, 1)
87	o.flipped = flips
88	r := portsmocks.NewMockRenderer(t)
89	pipeBuf := func() ports.DMABuf {
90		f, w, _ := os.Pipe()
91		w.Close()
92		return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
93	}
94	r.EXPECT().SetHDR(float64(0)).Return().Maybe()
95	r.EXPECT().ExportTargets(2, mock.Anything).Return([]ports.DMABuf{pipeBuf(), pipeBuf()}, nil).Once()
96	r.EXPECT().UseTarget(mock.Anything).Return()
97	r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
98	r.EXPECT().Close().Return().Once()
99	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
100	k.EXPECT().createBlob(mock.Anything).Return(99, nil)
101	k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
102	k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
103	frames := func() (commitRec, int) {
104		commitMu.Lock()
105		defer commitMu.Unlock()
106		n, last := 0, commitRec{}
107		for _, c := range *commits {
108			if c.user&3 == userFrame {
109				n, last = n+1, c
110			}
111		}
112		return last, n
113	}
114	scenes := make(chan ports.Scene, 1)
115	ctx, cancel := context.WithCancel(context.Background())
116	done := make(chan error, 1)
117	go func() {
118		done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, make(chan bool), scenes, nil, nil, make(chan ports.OutputPresented, 8), nil, nil)
119	}()
120	// A composed fullscreen window: a game frame under VRR.
121	game := ports.Scene{OutputWidth: 200, OutputHeight: 100,
122		Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 200, H: 100}, Fullscreen: true}},
123		Layers:  []ports.SceneLayer{{ID: 5, Layer: ports.LayerOverlay, Rect: ports.Rect{W: 10, H: 10}}}}
124	scenes <- game
125	waitFor(t, func() bool { _, n := frames(); return n == 1 })
126	last, _ := frames()
127	// The next frame is already waiting when the flip arrives.
128	game.Seq = 1
129	scenes <- game
130	flips <- flipEvent{crtc: tCrtc, user: last.user, when: time.Second, seq: 1}
131	// Run handles the flip after it is sent: its commit is at least the
132	// gap after this point, whatever the scheduling delay.
133	sent := time.Now()
134	waitFor(t, func() bool { _, n := frames(); return n == 2 })
135	commitMu.Lock()
136	second := (*commits)[len(*commits)-1].at
137	commitMu.Unlock()
138	if waited := second.Sub(sent); waited < o.vrrFlipGap-time.Millisecond {
139		t.Fatalf("next frame committed %s after the flip, gap %s", waited, o.vrrFlipGap)
140	}
141	cancel()
142	if err := <-done; err != nil {
143		t.Fatal(err)
144	}
145}

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