plat/linux_gpuout.rs
1// linux_gpuout -- what lib/gpu's images need from the window system: dmabuf buffers on a window,
2// through JFLib's own Wayland and X11 code (programs against glibc only; docs/GPU.md "Ausgabe unter
3// Linux"). No libwayland, no libxcb: Vulkan's surfaces would want a pointer from them, so the GPU
4// draws into images of its own and this code hands them to the window.
5//
6// win_gpu_child(w) -> NativeWindow what the display accepts for window w (format + modifier
7// pairs, its render device) and the window's size: None = the
8// CPU path. The name is the Windows presenter's, whose child
9// window this stands for; a Linux window needs no child: one
10// wl_surface or X window takes shared memory and dmabufs in turn
11// win_gpu_child_show, win_release_kept, win_in_sizemove, win_display_adapter, win_remote_session
12// nothing to do or know here
13// gpu_buf_new(w, &GpuBufDesc) -> handle a buffer of window w from its planes (takes the fds);
14// 0: refused. On Wayland its answer comes later: PENDING first
15// gpu_buf_state(w, handle) -> GPU_BUF_* FREE: nobody reads it, it is not the one on the window
16// gpu_buf_free(w, handle) the buffer goes (also while it is on the window: what it shows
17// stays, as long as nobody writes the memory)
18// gpu_can_show(w) -> bool a new frame is welcome now: the last commit has been answered
19// (the frame callback, PresentCompleteNotify; 1 s at most)
20// gpu_show(w, handle, rects) the buffer, finished by the GPU, goes on the window (at once, or
21// as soon as the window can take it); rects: what changed against
22// the last one, None = all. From now on window w shows through
23// buffers, until the next win_present.
24// gpu_next_look(w) -> time_us when a frame that cannot go on yet should look again; the wait
25// returns earlier when the answer comes
26// gpu_wait(deadline_us) pump the display until the deadline, also with events queued
27// (they stay queued; a plain wait would not read the socket)
28//
29// Only the thread of the window loop calls these (the backends are single-threaded: linux_disp.rs).
30//
31// No wrong frame can show:
32// * a buffer goes to the window only after the GPU has finished it (lib/gpu: Swapchain::flush
33// looks at the timeline without waiting). So nothing depends on implicit sync: at the attach no
34// fence is pending on the buffer, on any compositor, kernel or driver.
35// * a buffer is drawn into only after the compositor gave it back (wl_buffer.release,
36// PresentIdleNotify) and no read of it is pending on its dma-buf (poll POLLOUT: the compositor's
37// GPU may still be reading it when the release arrives), and never while it is the newest.
38// * win_present ends the buffers' turn: a frame still on its way is dropped, shared memory shows
39// the whole frame.
40// Test aids: win_test_image(w) reads what the window system holds where it can (X11: GetImage).
41
42use alloc::vec::Vec;
43
44use super::disp::{disp_backend, BK_WAYLAND, BK_X11};
45
46/// a buffer's state (gpu_buf_state)
47pub const GPU_BUF_PENDING: u32 = 0;
48pub const GPU_BUF_FREE: u32 = 1;
49pub const GPU_BUF_BUSY: u32 = 2;
50pub const GPU_BUF_FAILED: u32 = 3;
51
52/// what a display accepts as buffers
53#[derive(Clone, Debug, Default)]
54pub struct GpuCaps {
55 /// the device it renders with (dev_t of a node of the DRM device), when it says so
56 pub device: Option<u64>,
57 /// DRM fourcc and modifier
58 pub formats: Vec<(u32, u64)>,
59}
60
61/// a plane of a buffer: a dma-buf (owned: closed once sent), its offset and row pitch
62#[derive(Clone, Copy, Debug)]
63pub struct GpuPlane {
64 pub fd: i32,
65 pub offset: u32,
66 pub stride: u32,
67}
68
69/// a buffer of memory the GPU drew into
70#[derive(Clone, Debug)]
71pub struct GpuBufDesc {
72 pub w: i32,
73 pub h: i32,
74 pub fourcc: u32,
75 pub modifier: u64,
76 pub planes: Vec<GpuPlane>,
77}
78
79/// the Linux form of the handles lib/gpu gets for a window (Windows: HWND, HINSTANCE)
80#[derive(Clone, Debug)]
81pub struct NativeWindow {
82 pub caps: GpuCaps,
83 pub size: (i32, i32),
84}
85
86pub fn win_gpu_child(w: i32) -> Option<NativeWindow> {
87 if crate::env("jfgui_GPU_DMABUF").as_deref() == Some("0") {
88 return None; // (tests: a display without them)
89 }
90 let caps = match disp_backend() {
91 BK_WAYLAND => super::wl::gpuw::gpu_caps(),
92 BK_X11 => super::x11::gpux::gpu_caps(w),
93 _ => None,
94 }?;
95 if caps.formats.is_empty() {
96 return None;
97 }
98 Some(NativeWindow { caps, size: crate::win_size(w) })
99}
100
101pub fn win_gpu_child_show(_w: i32, _show: bool) {}
102
103pub fn win_release_kept(_w: i32) {}
104
105/// no child now, but it may come (the host or the next frame wakes the loop): only Android
106pub fn win_gpu_child_later(_w: i32) -> bool {
107 false
108}
109
110/// the window still shows on its child alone: here always
111pub fn win_gpu_child_alone(_w: i32) -> bool {
112 true
113}
114
115/// how the output came up (GPU_STATE_*): Android keeps it across runs
116pub fn gpu_out_state(_state: u32) {}
117
118/// a thread for the output's jobs
119pub fn gpu_thread_spawn(stack: usize, f: alloc::boxed::Box<dyn FnOnce() + Send>) -> Option<crate::Thread> {
120 crate::thread_spawn(stack, f)
121}
122
123pub fn win_in_sizemove() -> bool {
124 false
125}
126
127/// the GPU that drives the window's monitor: not known here, the display's device decides (GpuCaps)
128pub fn win_display_adapter(_w: i32) -> Option<(u32, u32)> {
129 None
130}
131
132pub fn win_remote_session() -> bool {
133 false
134}
135
136pub fn gpu_buf_new(w: i32, d: &GpuBufDesc) -> u32 {
137 if crate::env("jfgui_GPU_DMABUF").as_deref() == Some("refuse") {
138 close_planes(d); // (tests: a display that refuses them)
139 return 0;
140 }
141 match disp_backend() {
142 BK_WAYLAND => super::wl::gpuw::gpu_buf_new(w, d),
143 BK_X11 => super::x11::gpux::gpu_buf_new(w, d),
144 _ => {
145 close_planes(d);
146 0
147 }
148 }
149}
150
151/// the descriptors of a buffer nobody takes
152pub(crate) fn close_planes(d: &GpuBufDesc) {
153 for p in &d.planes {
154 // SAFETY: closes a descriptor the caller handed over.
155 unsafe { rt::os::syscall3(rt::os::SYS_CLOSE, p.fd as usize, 0, 0) };
156 }
157}
158
159pub fn gpu_buf_state(w: i32, h: u32) -> u32 {
160 match disp_backend() {
161 BK_WAYLAND => super::wl::gpuw::gpu_buf_state(w, h),
162 BK_X11 => super::x11::gpux::gpu_buf_state(w, h),
163 _ => GPU_BUF_FAILED,
164 }
165}
166
167pub fn gpu_buf_free(w: i32, h: u32) {
168 match disp_backend() {
169 BK_WAYLAND => super::wl::gpuw::gpu_buf_free(w, h),
170 BK_X11 => super::x11::gpux::gpu_buf_free(w, h),
171 _ => {}
172 }
173}
174
175pub fn gpu_can_show(w: i32) -> bool {
176 match disp_backend() {
177 BK_WAYLAND => super::wl::gpuw::gpu_can_show(w),
178 BK_X11 => super::x11::gpux::gpu_can_show(w),
179 _ => false,
180 }
181}
182
183pub fn gpu_show(w: i32, h: u32, rects: Option<&[[i32; 4]]>) {
184 match disp_backend() {
185 BK_WAYLAND => super::wl::gpuw::gpu_show(w, h, rects),
186 BK_X11 => super::x11::gpux::gpu_show(w, h, rects),
187 _ => {}
188 }
189}
190
191pub fn gpu_next_look(w: i32) -> u64 {
192 match disp_backend() {
193 BK_WAYLAND => super::wl::gpuw::gpu_next_look(w),
194 BK_X11 => super::x11::gpux::gpu_next_look(w),
195 _ => u64::MAX,
196 }
197}
198
199pub fn gpu_wait(deadline_us: u64) {
200 let now = crate::time_us();
201 let ms = (deadline_us.saturating_sub(now).div_ceil(1000)).min(1000) as i32;
202 match disp_backend() {
203 BK_WAYLAND => super::wl::gpuw::gpu_pump(ms),
204 BK_X11 => super::x11::gpux::gpu_pump(ms),
205 _ => crate::sleep_ms(ms as u32),
206 }
207}
208
209// ------------------------------------------------------------------ test aids, as on Windows
210
211pub const WT_PIXEL: u32 = 1;
212pub const WT_REDRAW: u32 = 2;
213pub const WT_MOVE: u32 = 3;
214pub const WT_DPI: u32 = 4;
215pub const WT_POPUP_RECT: u32 = 7;
216pub const WT_NOANIM: u32 = 16;
217pub const WT_FROM_POINT: u32 = 17;
218pub const WT_SHOW: u32 = 18;
219
220/// (tests)
221/// WT_PIXEL the colour the window system holds at client pixel a[0], a[1] of window w: X11
222/// GetImage, Wayland the last shared memory buffer; -1: no way to know (Wayland on
223/// GPU buffers: a client cannot read a window back)
224/// WT_MOVE a[0], a[1], a[2], a[3]: the window's rectangle; win_set_rect reports nothing back
225/// here, the event a size change has on Windows comes from this aid
226/// WT_DPI a[0]: the dpi as if the compositor preferred that scale (Wayland with a viewport,
227/// else -1: X11 has one dpi for the display and no second size)
228/// WT_SHOW a[0] = 7 minimised, 4 back: X11 through the window manager; Wayland can minimise, but
229/// no client can undo that: 4 does what the compositor's next activation does here
230/// WT_POPUP_RECT popup slot w is shown: 1, else -1
231/// WT_NOANIM, WT_FROM_POINT nothing to turn off; nobody tells a client what lies on top of its
232/// window: it counts as ours
233pub fn win_test(op: u32, w: i32, a: [i32; 4]) -> i64 {
234 match op {
235 WT_PIXEL => match disp_backend() {
236 BK_WAYLAND => match super::wl::gpuw::gpu_test_shm(w) {
237 Some((iw, ih, px)) if a[0] >= 0 && a[1] >= 0 && a[0] < iw && a[1] < ih => {
238 let o = (a[1] as usize * iw as usize + a[0] as usize) * 4;
239 px[o] as i64 | (px[o + 1] as i64) << 8 | (px[o + 2] as i64) << 16
240 }
241 _ => -1,
242 },
243 BK_X11 => super::x11::gpux::gpu_test_pixel(w, a[0], a[1]),
244 _ => -1,
245 },
246 WT_MOVE => {
247 crate::win_set_rect(w, a[0], a[1], a[2], a[3]);
248 crate::evq_push(crate::Event { typ: crate::E_RESIZE, x: a[2], y: a[3], win: w as u8, ..crate::Event::default() });
249 1
250 }
251 WT_DPI => match disp_backend() {
252 BK_WAYLAND => super::wl::gpuw::gpu_test_dpi(w, a[0]),
253 _ => -1,
254 },
255 WT_SHOW => {
256 if a[0] == 7 {
257 crate::win_cmd(w, crate::WCMD_MINIMIZE);
258 } else if disp_backend() == BK_WAYLAND {
259 super::wl::gpuw::gpu_test_unminimize(w);
260 } else {
261 crate::win_cmd(w, crate::WCMD_RESTORE);
262 }
263 1
264 }
265 WT_POPUP_RECT => match disp_backend() {
266 BK_WAYLAND => super::wl::gpuw::gpu_test_popup(w),
267 BK_X11 => super::x11::gpux::gpu_test_popup(w),
268 _ => -1,
269 },
270 _ => 1,
271 }
272}
273
274/// (tests) what the window system holds for window w: (w, h, rows of w * 4 bytes): X11 reads it
275/// back (GetImage; also a buffer on the window), Wayland has no way to: None
276pub fn win_test_image(w: i32) -> Option<(i32, i32, Vec<u8>)> {
277 match disp_backend() {
278 BK_X11 => super::x11::gpux::gpu_test_image(w),
279 _ => None,
280 }
281}
282
283/// (tests) a part of the screen: no client on Linux can read it without asking the user
284pub fn win_test_screen(_x: i32, _y: i32, _w: i32, _h: i32) -> Option<Vec<u8>> {
285 None
286}
287
288/// (tests) nothing to send to: Windows' messages
289pub fn win_test_send(_w: i32, _msg: u32, _wp: usize, _lp: isize) -> isize {
290 0
291}
292
293/// (tests) (CPU time of this process so far in us, its resident bytes, its anonymous resident bytes)
294pub fn win_test_usage() -> (u64, u64, u64) {
295 let mut ts = [0i64; 2];
296 // SAFETY: CLOCK_PROCESS_CPUTIME_ID into a timespec.
297 unsafe { rt::os::syscall3(rt::os::SYS_CLOCK_GETTIME, 2, ts.as_mut_ptr() as usize, 0) };
298 let cpu = ts[0] as u64 * 1_000_000 + ts[1] as u64 / 1000;
299 let (mut rss, mut anon) = (0, 0);
300 if let Some(b) = super::sys::read_file_raw("/proc/self/status") {
301 for ln in b.split(|&c| c == b'\n') {
302 let val = |n: &[u8]| {
303 ln.strip_prefix(n).map_or(0u64, |r| r.iter().filter(|c| c.is_ascii_digit()).fold(0, |v, c| v * 10 + (c - b'0') as u64) * 1024)
304 };
305 rss = rss.max(val(b"VmRSS:"));
306 anon = anon.max(val(b"RssAnon:"));
307 }
308 }
309 (cpu, rss as u64, anon as u64)
310}
311
312/// (bench: jfgui_WL_FEEDBACK=1 on Wayland) the commits since the last call and when each reached the
313/// screen (wp_presentation): (time of the commit, time on the screen; 0: discarded), time_us
314pub fn gpu_test_presented() -> Vec<(u64, u64)> {
315 match disp_backend() {
316 BK_WAYLAND => super::wl::gpuw::gpu_test_presented(),
317 _ => Vec::new(),
318 }
319}