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