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) {}