Skip to main content

plat/
android_gpu.rs

1// android_gpu -- what the GPU layer and the UI's output through the GPU need of Android
2// (lib/gpu vk_load.rs, vk_swap.rs; lib/ui/gpu_out.rs; docs/GPU.md "Ausgabe unter Android")
3//
4//   dl_open(name) / dl_sym(handle, name) / dl_close(handle)   libvulkan.so at run time: no
5//                                      DT_NEEDED, the program starts on a phone without it
6//   gpu_thread_spawn(stack, f)         a real thread for the output's jobs.  thread_spawn stays
7//                                      None here: the pools and tasks of this target are cooperative
8//                                      and start nothing behind the UI's back; nothing but the
9//                                      output's jobs, which end by themselves, runs on these
10//   win_gpu_child(w)                   the surface of the SurfaceView (ANativeWindow), the analogue
11//                                      of Windows' child window; only for window 0 while nothing
12//                                      else shows on the canvas (tool windows and popups are
13//                                      composed on the CPU, below the surface)
14//   win_gpu_child_later(w)             no surface now, a frame or the host brings it: not a reason
15//                                      to give up, and nothing to poll for
16//   win_gpu_child_show(w, show)        show: the frame is on the surface, the canvas path rests;
17//                                      hide: the frame is on its way to the Bitmap, the host takes
18//                                      the SurfaceView away (the surface goes with it)
19//   win_release_kept(w)                the canvas's copy of the window goes
20//
21// The surface comes and goes with the system (GalleryActivity's SurfaceHolder.Callback, bridge.c):
22// jfgui_gpu_surface(window) on the UI thread.  bridge.c owns the ANativeWindow reference; the
23// VkSurfaceKHR takes its own, so a worker still building on a surface that went away gets errors,
24// not a dangling pointer.
25
26use alloc::boxed::Box;
27use core::{
28    ffi::c_void,
29    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
30};
31
32#[link(name = "dl")]
33unsafe extern "C" {
34    fn dlopen(name: *const u8, flags: i32) -> *mut c_void;
35    fn dlsym(handle: *mut c_void, name: *const u8) -> *mut c_void;
36    fn dlclose(handle: *mut c_void) -> i32;
37}
38
39#[repr(C, align(8))]
40struct PthreadAttr([u64; 8]);
41
42#[link(name = "c")]
43unsafe extern "C" {
44    fn pthread_attr_init(attr: *mut PthreadAttr) -> i32;
45    fn pthread_attr_setstacksize(attr: *mut PthreadAttr, size: usize) -> i32;
46    fn pthread_attr_destroy(attr: *mut PthreadAttr) -> i32;
47    fn pthread_create(out: *mut usize, attr: *const PthreadAttr, start: extern "C" fn(*mut c_void) -> *mut c_void, arg: *mut c_void) -> i32;
48    fn pthread_join(thread: usize, out: *mut *mut c_void) -> i32;
49}
50
51/// open a shared library (a name as the linker searches it, or a path) -> its handle
52pub fn dl_open(name: &str) -> Option<usize> {
53    let mut z = alloc::vec::Vec::with_capacity(name.len() + 1);
54    z.extend_from_slice(name.as_bytes());
55    z.push(0);
56    // SAFETY: z is NUL-terminated and outlives the call; RTLD_NOW (2), local to this library.
57    let h = unsafe { dlopen(z.as_ptr(), 2) };
58    (!h.is_null()).then_some(h as usize)
59}
60
61/// a symbol of the library (name NUL-terminated) -> its address, 0 when missing
62pub fn dl_sym(handle: usize, name: &[u8]) -> usize {
63    debug_assert!(name.last() == Some(&0));
64    // SAFETY: a handle of dl_open; name is NUL-terminated.
65    unsafe { dlsym(handle as *mut c_void, name.as_ptr()) as usize }
66}
67
68/// give the library back
69pub fn dl_close(handle: usize) {
70    // SAFETY: a handle of dl_open, closed once.
71    unsafe { dlclose(handle as *mut c_void) };
72}
73
74type Start = Box<dyn FnOnce() + Send>;
75
76extern "C" fn thread_main(arg: *mut c_void) -> *mut c_void {
77    // SAFETY: gpu_thread_spawn hands over this box, once.
78    let f = unsafe { Box::from_raw(arg as *mut Start) };
79    f();
80    core::ptr::null_mut()
81}
82
83/// a real thread with stack bytes of stack (the output's jobs) -> its handle
84pub fn gpu_thread_spawn(stack: usize, f: Start) -> Option<super::Thread> {
85    let arg = Box::into_raw(Box::new(f));
86    let mut attr = PthreadAttr([0; 8]);
87    let mut handle = 0usize;
88    // SAFETY: attr is at least as large as bionic's pthread_attr_t and lives across the calls;
89    // thread_main takes the box when the thread started, else it is taken back below.
90    let r = unsafe {
91        pthread_attr_init(&mut attr);
92        pthread_attr_setstacksize(&mut attr, stack);
93        let r = pthread_create(&mut handle, &attr, thread_main, arg as *mut c_void);
94        pthread_attr_destroy(&mut attr);
95        r
96    };
97    if r != 0 {
98        // SAFETY: no thread started, the box is still ours.
99        drop(unsafe { Box::from_raw(arg) });
100        return None;
101    }
102    Some(super::Thread { handle })
103}
104
105pub(super) fn thread_join(handle: usize) {
106    // SAFETY: a thread of gpu_thread_spawn, joined once (Thread::join takes self).
107    unsafe { pthread_join(handle, core::ptr::null_mut()) };
108}
109
110#[link(name = "android")]
111unsafe extern "C" {
112    fn ANativeWindow_acquire(window: usize);
113    fn ANativeWindow_release(window: usize);
114}
115
116/// a target for swapchains: the ANativeWindow of the SurfaceView, with a reference of its own (a
117/// worker may get to it after the surface went)
118#[derive(Debug)]
119pub struct NativeWindow {
120    pub window: usize,
121}
122
123impl Drop for NativeWindow {
124    fn drop(&mut self) {
125        // SAFETY: the reference win_gpu_child took.
126        unsafe { ANativeWindow_release(self.window) };
127    }
128}
129
130// the surface's ANativeWindow, 0 while there is none
131static SURFACE: AtomicUsize = AtomicUsize::new(0);
132
133/// (bridge.c) the surface came (an ANativeWindow) or went (0; nothing uses it when this returns)
134/// -> 1: the frame waits to go to the canvas, the host renders once more
135#[unsafe(no_mangle)]
136pub extern "C" fn jfgui_gpu_surface(window: usize) -> u32 {
137    SURFACE.store(window, Ordering::Release);
138    ASKED.store(false, Ordering::Release);
139    (window == 0 && super::surface_gone()) as u32
140}
141
142// GalleryHost.call -> GalleryActivity.gpuCall: 0 the SurfaceView away, 1 back, 2 .. 5 GPU_STATE_*
143const CALL_GPU: u32 = 60;
144
145fn ask(what: u8) {
146    let _ = super::android_host::call(CALL_GPU, &[what]);
147}
148
149// the SurfaceView was asked to come back, its surface is on the way
150static ASKED: AtomicBool = AtomicBool::new(false);
151
152/// how the output came up: the host keeps it across runs, the program's resolution follows
153pub fn gpu_out_state(state: u32) {
154    ask(state as u8);
155}
156
157/// the surface for window w; None: see win_gpu_child_later
158pub fn win_gpu_child(w: i32) -> Option<NativeWindow> {
159    if w != 0 || !super::windows::only_main() {
160        return None;
161    }
162    match SURFACE.load(Ordering::Acquire) {
163        0 => {
164            if !ASKED.swap(true, Ordering::AcqRel) {
165                ask(1); // (the host wakes the loop when it is there)
166            }
167            None
168        }
169        window => {
170            // SAFETY: bridge.c holds a reference while SURFACE has it, on this thread.
171            unsafe { ANativeWindow_acquire(window) };
172            Some(NativeWindow { window })
173        }
174    }
175}
176
177/// no surface for window w now, but it may come: the host or the next frame wakes the loop
178pub fn win_gpu_child_later(w: i32) -> bool {
179    w != 0 || !super::windows::only_main() || (SURFACE.load(Ordering::Acquire) == 0 && ASKED.load(Ordering::Acquire))
180}
181
182/// the window still shows on its surface alone: nothing else came on the canvas
183pub fn win_gpu_child_alone(w: i32) -> bool {
184    w == 0 && super::windows::only_main()
185}
186
187/// show: the window shows on the surface; hide: on the canvas, the surface goes
188pub fn win_gpu_child_show(w: i32, show: bool) {
189    if w == 0 {
190        super::windows::gpu_shows(show);
191        // (when the system took the surface the SurfaceView stays: the surface comes back with it)
192        if !show && SURFACE.load(Ordering::Acquire) != 0 {
193            ask(0);
194        }
195    }
196}
197
198/// the frame is on the surface: the canvas's copy of the window goes (it comes back whole)
199pub fn win_release_kept(w: i32) {
200    super::windows::release_pixels(w);
201}
202
203/// no modal size loop here
204pub fn win_in_sizemove() -> bool {
205    false
206}
207
208/// one GPU, which drives the display
209pub fn win_display_adapter(_w: i32) -> Option<(u32, u32)> {
210    None
211}
212
213/// no remote sessions here
214pub fn win_remote_session() -> bool {
215    false
216}
217
218// the script command `gpu` (a script's real windows) has nothing to work with here: scripts run on
219// the desktop, this target has its own test (apps/androidGallery/tests/android_gpu.py)
220pub const WT_NOANIM: u32 = 0;
221pub const WT_FROM_POINT: u32 = 0;
222pub fn win_test(_op: u32, _w: i32, _a: [i32; 4]) -> i64 {
223    0
224}
225pub fn win_test_image(_w: i32) -> Option<(i32, i32, alloc::vec::Vec<u8>)> {
226    None
227}
228pub fn win_test_screen(_x: i32, _y: i32, _w: i32, _h: i32) -> Option<alloc::vec::Vec<u8>> {
229    None
230}
231pub fn gpu_wait(_deadline_us: u64) {}