frame_submit.go RawHistoryFolder

1package drm
2
3import (
4	"context"
5	"time"
6
7	"github.com/bnema/neferwl/internal/adapters/capture"
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11// renderError is a frame the renderer failed to draw: the output stops.
12type renderError struct{ err error }
13
14func (e renderError) Error() string { return "render frame: " + e.err.Error() }
15func (e renderError) Unwrap() error { return e.err }
16
17// submitFrame puts one frame of the scene on screen: the fullscreen window
18// scanned out directly when the kernel takes it, else a composed frame with
19// an optional overlay plane, dropped when the kernel refuses it. Captures
20// force composition and are answered from the composed frame; on renderError
21// the caller still owns the requests and must fail them. direct reports scanout.
22//
23// On success the frame is in flight; errOverlayDropped asks for a new frame
24// without the overlay; a renderError stops the output; other errors are
25// commit errors for commitFailed.
26func (o *Output) submitFrame(ctx context.Context, r ports.Renderer, scene ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent, seen map[ports.WindowID]uint64, requests []ports.CaptureRequest, pipeline *capture.Pipeline) (direct bool, err error) {
27	presented := false // the frame was committed: what it gated is released
28	if !o.sceneCurrent(scene) {
29		return false, errSecurityScene
30	}
31	if o.protected {
32		return false, o.submitProtectedFrame(ctx, r, scene, surfaces, seen)
33	}
34	// shows is what the frame puts on screen: presentation feedback of
35	// windows it does not draw is discarded, not presented.
36	f := pendingFrame{security: scene.Security, shows: o.shownBy(scene, seen)}
37	o.wantContent(scene, surfaces)
38	// Requests that cannot be served (exclusion of another session, a
39	// workspace that moved) are failed here, never answered from the
40	// displayed frame.
41	// A request whose capture indicator the scene does not show yet is held
42	// (the caller keeps it, see capture.Waiting).
43	ready, _ := pipeline.Hold(scene, requests, time.Now())
44	if len(scene.CaptureIndicators) > 0 {
45		// The captures of this frame are delivered only once the frame that
46		// carries their indicator is committed, whatever the path out (see
47		// capture.Pipeline.EndGate): no failed or dropped frame serves one.
48		pipeline.BeginGate()
49		defer func() {
50			if err == nil && !presented {
51				err = capture.ErrFrameNotPresented // a panic on the way out
52			}
53			pipeline.EndGate(capture.GateVerdict(err))
54		}()
55	}
56	normal, excluded, hidden := pipeline.Split(scene, ready)
57	pipeline.Retire(scene)
58	if len(hidden) > 0 {
59		if !o.sceneCurrent(scene) || o.protected {
60			return false, errSecurityScene
61		}
62		// A hidden workspace: the child renderer draws it, whatever the display
63		// does (scanout included). Failures answer the requests and never stop
64		// the output.
65		pipeline.SubmitHidden(scene, surfaces, hidden)
66		// Publish child holds before queuing a display flip, which may stall
67		// past the stale-report timeout. Do not advance the display's Seen
68		// until its own GPU work finishes.
69		if o.capHidden != nil {
70			o.report(nil, o.reports.Seen())
71		}
72		clear(hidden)
73	}
74	// Displayed-frame captures, Exclude requests and the capture indicator
75	// force composition (scanout and the overlay plane would skip them); the
76	// hidden-workspace child does not.
77	decision := o.decideFrame(scene, surfaces, len(normal) > 0 || len(excluded) > 0 || len(scene.CaptureIndicators) > 0)
78	if !o.sceneCurrent(scene) || o.protected {
79		decision.overlay.close()
80		return false, errSecurityScene
81	}
82	if decision.fb != 0 {
83		c := decision.content
84		if direct, err = o.commitScanoutRect(decision.fb, c, pendingFrame{security: scene.Security, shows: o.directShownBy(c.ID, seen[c.ID])}, decision.rect); direct {
85			presented = err == nil
86			return true, err
87		}
88		decision = o.composeFrame(scene, surfaces)
89	}
90	ov, composed := decision.overlay, decision.composed
91	r.UseTarget(o.back)
92	// Captures never show the capture indicator: their frame goes first.
93	took, pdone, perr := pipeline.SubmitPlain(r, composed, surfaces, normal)
94	if perr != nil {
95		o.holdRead(pdone)
96		if pdone != nil {
97			pdone.Close()
98		}
99		ov.close()
100		return false, renderError{perr}
101	}
102	// Not capture admission: an engage during the render retains its client
103	// reads; SubmitScoped already failed the requests of a stale epoch.
104	if !o.sceneCurrent(scene) || o.protected {
105		o.holdRead(pdone)
106		if pdone != nil {
107			pdone.Close()
108		}
109		ov.close()
110		return false, errSecurityScene
111	}
112	if pdone != nil {
113		pdone.Close()
114	}
115	if took {
116		normal = nil // answered from the frame without the indicator
117	}
118	if len(excluded) > 0 {
119		// The excluded frame goes first to the same target and is copied by
120		// the capture; the displayed frame is drawn over it in queue order.
121		// Cost: a second composition on frames with an excluded request.
122		xdone, xerr := r.Render(pipeline.ExcludedScene(composed), surfaces)
123		if xerr != nil {
124			o.holdRead(xdone)
125			if xdone != nil {
126				xdone.Close()
127			}
128			ov.close()
129			return false, renderError{xerr}
130		}
131		// An excluded composition is not capture admission. Engage may have
132		// happened while Render submitted GPU work; retain its client reads
133		// and leave the request credit with the caller on rejection.
134		if !o.sceneCurrent(scene) || o.protected {
135			o.holdRead(xdone)
136			if xdone != nil {
137				xdone.Close()
138			}
139			ov.close()
140			return false, errSecurityScene
141		}
142		if xdone != nil {
143			xdone.Close()
144		}
145		pipeline.SubmitScoped(scene.Security, r, excluded)
146		// Ownership moved to the worker (or an immediate failure reply).
147		clear(excluded)
148	}
149	done, rerr := r.Render(composed, surfaces)
150	if rerr != nil {
151		ov.close()
152		return false, renderError{rerr}
153	}
154	if ov.fb != 0 && !o.testOverlay(o.fbs[o.back], ov) {
155		// Refused: compose the window too (cached per buffer).
156		if done != nil {
157			done.Close()
158		}
159		ov.close()
160		ov = overlayWin{}
161		if done, rerr = r.Render(scene, surfaces); rerr != nil {
162			return false, renderError{rerr}
163		}
164	}
165	if !o.sceneCurrent(scene) || o.protected {
166		o.holdRead(done)
167		if done != nil {
168			done.Close()
169		}
170		ov.close()
171		return false, errSecurityScene
172	}
173	if len(normal) > 0 {
174		// Scanout and the overlay plane were skipped for this frame.
175		o.log.Debug().Str("connector", o.conn.name).Int("captures", len(normal)).Msg("capture frame composed")
176		pipeline.SubmitScoped(scene.Security, r, normal)
177		// Ownership moved to the worker (or an immediate failure reply).
178		// A later commit panic must not fail the same requests again.
179		clear(normal)
180	}
181	if !o.sceneCurrent(scene) || o.protected {
182		o.holdRead(done)
183		if done != nil {
184			done.Close()
185		}
186		ov.close()
187		return false, errSecurityScene
188	}
189	// The overlay buffer is on screen like a scanned-out one: it is
190	// reported shown, so it is not released under the plane.
191	f.queued, f.zeroCopy, f.composed = ov.buf, ov.id, true
192	// A covering fullscreen window keeps VRR while composed (overlay
193	// layer, subsurfaces, size mismatch).
194	game := o.vrrProp != 0 && fullscreenShown(&scene)
195	err = o.commitWith(o.fbs[o.back], done, false, o.wantVRR(game), f, ov)
196	presented = err == nil
197	if err != nil {
198		// The GPU may still read client buffers for this frame.
199		o.holdRead(done)
200	}
201	if done != nil {
202		done.Close()
203	}
204	ov.close()
205	if err != nil && o.overlayConflict(err, ov.buf) {
206		return false, errOverlayDropped
207	}
208	if err == nil {
209		o.queued = ov.buf
210		o.back = 1 - o.back
211	}
212	return false, err
213}

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