Skip to main content

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}