1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6#[repr(i32)]
7pub enum AndroidRenderingMode {
8 Software = 1,
10
11 Egl = 2,
13
14 Vulkan = 3,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct AndroidPlatformOptions {
22 pub rendering_mode: Vec<AndroidRenderingMode>,
31}
32
33impl Default for AndroidPlatformOptions {
34 fn default() -> Self {
35 Self { rendering_mode: vec![AndroidRenderingMode::Egl, AndroidRenderingMode::Software] }
36 }
37}
38
39#[cfg_attr(not(target_os = "android"), allow(dead_code))]
52pub(crate) fn initialize_graphics_core<G>(
53 opts: &AndroidPlatformOptions,
54 try_create_egl: &mut dyn FnMut() -> Option<G>,
55 try_create_vulkan: &mut dyn FnMut() -> Option<G>,
56) -> Option<G> {
57 if opts.rendering_mode.is_empty() {
58 panic!("AndroidPlatformOptions.rendering_mode must not be empty or null");
59 }
60
61 for rendering_mode in &opts.rendering_mode {
62 match rendering_mode {
63 AndroidRenderingMode::Software => return None,
64 AndroidRenderingMode::Egl => {
65 if let Some(egl) = try_create_egl() {
66 return Some(egl);
67 }
68 }
69 AndroidRenderingMode::Vulkan => {
70 if let Some(vulkan) = try_create_vulkan() {
71 return Some(vulkan);
72 }
73 }
74 }
75 }
76
77 panic!(
78 "AndroidPlatformOptions.rendering_mode has a value of \"{}\", but no options were applied.",
79 opts.rendering_mode.iter().map(|mode| format!("{mode:?}")).collect::<Vec<_>>().join(", ")
80 );
81}
82
83#[cfg(target_os = "android")]
84pub use imp::{AndroidApplicationExtensions, AndroidPlatform};
85
86#[cfg(target_os = "android")]
87mod imp {
88 use super::{initialize_graphics_core, AndroidPlatformOptions};
89 use crate::android_dispatcher_impl::AndroidDispatcherImpl;
90 use crate::android_egl::AndroidEglPlatformGraphics;
91 use crate::android_runtime_platform::AndroidRuntimePlatformServices;
92 use crate::choreographer_timer::ChoreographerTimer;
93 use crate::cursor_factory::CursorFactory;
94 use crate::platform::AndroidActivatableLifetime;
95 use crate::stubs::{PlatformIconLoaderStub, WindowingPlatformStub};
96 use ferroui_base::input::platform::{KeyGestureFormatInfo, PlatformHotkeyConfiguration};
97 use crate::ferro_android_application::FerroAndroidApplication;
98 use crate::platform::android_platform_settings::ApplicationSettingsContext;
99 use crate::platform::{AndroidKeyboardDevice, AndroidPlatformSettings};
100 use ferroui_base::input::IKeyboardDevice;
101 use ferroui_base::platform::{ICursorFactory, IPlatformGraphics, IPlatformSettings};
102 use ferroui_base::rendering::composition::Compositor;
103 use ferroui_base::rendering::{IRenderLoop, IRenderTimer, RenderLoop};
104 use ferroui_base::threading::Dispatcher;
105 use ferroui_base::{FerroLocator, LocatorExtensions};
106 use ferroui_controls::application_lifetimes::IActivatableLifetime;
107 use ferroui_controls::platform::{IPlatformIconLoader, IWindowingPlatform};
108 use ferroui_controls::AppBuilder;
109 use ferroui_harfbuzz::HarfBuzzApplicationExtensions;
110 use ferroui_skia::SkiaApplicationExtensions;
111 use std::cell::RefCell;
112 use std::collections::HashMap;
113 use std::rc::Rc;
114 use std::sync::Arc;
115
116 pub trait AndroidApplicationExtensions {
118 fn use_android(&self) -> AppBuilder;
122 }
123
124 impl AndroidApplicationExtensions for AppBuilder {
125 fn use_android(&self) -> AppBuilder {
126 AndroidRuntimePlatformServices::use_android_runtime_platform_subsystem(self)
127 .use_windowing_subsystem(AndroidPlatform::initialize, "Android")
128 .use_harfbuzz()
129 .use_skia()
130 }
131 }
132
133 ferroui_base::ferro_thread_local! {
134 static OPTIONS: RefCell<Option<Rc<AndroidPlatformOptions>>> = const { RefCell::new(None) };
135 static COMPOSITOR: RefCell<Option<Rc<Compositor>>> = const { RefCell::new(None) };
136 static TIMER: RefCell<Option<Arc<ChoreographerTimer>>> = const { RefCell::new(None) };
137 }
138
139 pub struct AndroidPlatform;
141
142 impl AndroidPlatform {
143 pub const INSTANCE: AndroidPlatform = AndroidPlatform;
146
147 pub fn options() -> Option<Rc<AndroidPlatformOptions>> {
149 OPTIONS.with(|options| options.borrow().clone())
150 }
151
152 pub(crate) fn compositor() -> Option<Rc<Compositor>> {
153 COMPOSITOR.with(|compositor| compositor.borrow().clone())
154 }
155
156 #[allow(dead_code)]
157 pub(crate) fn timer() -> Option<Arc<ChoreographerTimer>> {
158 TIMER.with(|timer| timer.borrow().clone())
159 }
160
161 pub fn initialize() {
169 let options = FerroLocator::current().get_service::<AndroidPlatformOptions>().unwrap_or_default();
170 OPTIONS.with(|slot| *slot.borrow_mut() = Some(options.clone()));
171
172 Dispatcher::initialize_ui_thread_dispatcher(AndroidDispatcherImpl::new());
173 let timer = Arc::new(ChoreographerTimer::new());
174 TIMER.with(|slot| *slot.borrow_mut() = Some(timer.clone()));
175
176 let windowing_platform: Rc<dyn IWindowingPlatform> = Rc::new(WindowingPlatformStub);
177 let keyboard_device: Rc<dyn IKeyboardDevice> = AndroidKeyboardDevice::new();
178 let platform_settings: Rc<dyn IPlatformSettings> = AndroidPlatformSettings::new(
179 FerroAndroidApplication::try_context()
180 .map(|context| Box::new(ApplicationSettingsContext::new(context)) as Box<_>),
181 );
182 let render_timer: Arc<dyn IRenderTimer> = timer;
183 let render_loop: Arc<dyn IRenderLoop> = RenderLoop::from_timer(render_timer);
184 let activatable_lifetime = AndroidActivatableLifetime::new();
185 let activatable: Rc<dyn IActivatableLifetime> = activatable_lifetime.clone();
186
187 let locator = FerroLocator::current_mutable();
188 locator
189 .bind::<dyn ICursorFactory>()
190 .to_transient::<CursorFactory>(|instance| instance)
191 .bind::<dyn IWindowingPlatform>()
192 .to_constant(windowing_platform)
193 .bind::<dyn IKeyboardDevice>()
194 .to_constant(keyboard_device)
195 .bind::<dyn IPlatformSettings>()
196 .to_constant(platform_settings)
197 .bind::<dyn IPlatformIconLoader>()
198 .to_singleton::<PlatformIconLoaderStub>(|instance| instance)
199 .bind::<Arc<dyn IRenderLoop>>()
200 .to_constant(Rc::new(render_loop))
201 .bind_to_self_singleton::<PlatformHotkeyConfiguration>()
202 .bind_to_self(Rc::new(KeyGestureFormatInfo::new(Some(HashMap::new()), "Cmd", "Ctrl", "Alt", "Shift")))
203 .bind::<dyn IActivatableLifetime>()
204 .to_constant(activatable)
205 .bind_to_self(activatable_lifetime);
207
208 let graphics = Self::initialize_graphics(&options);
209 if let Some(graphics) = &graphics {
210 locator.bind::<Arc<dyn IPlatformGraphics>>().to_constant(Rc::new(graphics.clone()));
211 }
212
213 let compositor = Compositor::new(graphics, false);
214 COMPOSITOR.with(|slot| *slot.borrow_mut() = Some(compositor.clone()));
215 locator.bind_to_self(compositor);
216 }
217
218 fn initialize_graphics(opts: &AndroidPlatformOptions) -> Option<Arc<dyn IPlatformGraphics>> {
219 initialize_graphics_core(
220 opts,
221 &mut || AndroidEglPlatformGraphics::try_create().map(|egl| Arc::new(egl) as Arc<dyn IPlatformGraphics>),
222 &mut Self::try_create_vulkan,
223 )
224 }
225
226 #[cfg(feature = "vulkan")]
229 fn try_create_vulkan() -> Option<Arc<dyn IPlatformGraphics>> {
230 use crate::platform::vulkan::VulkanSupport;
231 use ferroui_vulkan::VulkanOptions;
232
233 let vulkan_options = FerroLocator::current().get_service::<VulkanOptions>().map(|options| (*options).clone());
234 VulkanSupport::try_initialize(vulkan_options).map(|vulkan| vulkan as Arc<dyn IPlatformGraphics>)
235 }
236
237 #[cfg(not(feature = "vulkan"))]
241 fn try_create_vulkan() -> Option<Arc<dyn IPlatformGraphics>> {
242 use ferroui_base::logging::{LogEventLevel, Logger};
243
244 if let Some(logger) = Logger::try_get(LogEventLevel::Warning, "Vulkan") {
245 logger.log(
246 None,
247 "The Vulkan rendering mode needs the feature `vulkan` of ferroui-android (Skia with Vulkan); the mode is passed over",
248 );
249 }
250 None
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
259
260 fn options(rendering_mode: Vec<AndroidRenderingMode>) -> AndroidPlatformOptions {
261 AndroidPlatformOptions { rendering_mode }
262 }
263
264 #[test]
265 fn the_default_order_is_egl_then_software() {
266 assert_eq!(
267 AndroidPlatformOptions::default().rendering_mode,
268 vec![AndroidRenderingMode::Egl, AndroidRenderingMode::Software]
269 );
270 assert_eq!(AndroidRenderingMode::Software as i32, 1);
271 assert_eq!(AndroidRenderingMode::Egl as i32, 2);
272 assert_eq!(AndroidRenderingMode::Vulkan as i32, 3);
273 }
274
275 #[test]
276 fn the_default_order_takes_egl_when_it_initializes() {
277 let mut asked = 0;
278 let graphics = initialize_graphics_core(
279 &AndroidPlatformOptions::default(),
280 &mut || {
281 asked += 1;
282 Some(7)
283 },
284 &mut || panic!("Vulkan is not in the default order"),
285 );
286 assert_eq!(graphics, Some(7));
287 assert_eq!(asked, 1);
288 }
289
290 #[test]
291 fn the_default_order_ends_at_software_when_egl_does_not_initialize() {
292 let mut asked = 0;
293 let graphics = initialize_graphics_core::<i32>(
294 &AndroidPlatformOptions::default(),
295 &mut || {
296 asked += 1;
297 None
298 },
299 &mut || panic!("Vulkan is not in the default order"),
300 );
301 assert_eq!(graphics, None);
302 assert_eq!(asked, 1);
303 }
304
305 #[test]
306 fn software_first_does_not_ask_for_egl() {
307 let graphics = initialize_graphics_core::<i32>(
308 &options(vec![AndroidRenderingMode::Software, AndroidRenderingMode::Egl]),
309 &mut || panic!("EGL must not be asked for"),
310 &mut || panic!("Vulkan must not be asked for"),
311 );
312 assert_eq!(graphics, None);
313 }
314
315 #[test]
316 #[should_panic(expected = "must not be empty or null")]
317 fn an_empty_list_fails() {
318 initialize_graphics_core::<i32>(&options(vec![]), &mut || None, &mut || None);
319 }
320
321 #[test]
322 #[should_panic(expected = "has a value of \"Egl, Vulkan\", but no options were applied.")]
323 fn a_list_in_which_nothing_applies_fails() {
324 initialize_graphics_core::<i32>(
325 &options(vec![AndroidRenderingMode::Egl, AndroidRenderingMode::Vulkan]),
326 &mut || None,
327 &mut || None,
328 );
329 }
330
331 #[test]
332 fn vulkan_is_taken_when_it_is_named_and_initializes() {
333 let asked = std::cell::RefCell::new(Vec::new());
334 let graphics = initialize_graphics_core(
335 &options(vec![AndroidRenderingMode::Vulkan, AndroidRenderingMode::Egl, AndroidRenderingMode::Software]),
336 &mut || {
337 asked.borrow_mut().push("egl");
338 Some(2)
339 },
340 &mut || {
341 asked.borrow_mut().push("vulkan");
342 Some(3)
343 },
344 );
345 assert_eq!(graphics, Some(3));
346 assert_eq!(*asked.borrow(), vec!["vulkan"]);
347 }
348
349 #[test]
350 fn vulkan_that_does_not_initialize_is_passed_over() {
351 let asked = std::cell::RefCell::new(Vec::new());
352 let graphics = initialize_graphics_core(
353 &options(vec![AndroidRenderingMode::Vulkan, AndroidRenderingMode::Egl]),
354 &mut || {
355 asked.borrow_mut().push("egl");
356 Some(2)
357 },
358 &mut || {
359 asked.borrow_mut().push("vulkan");
360 None
361 },
362 );
363 assert_eq!(graphics, Some(2));
364 assert_eq!(*asked.borrow(), vec!["vulkan", "egl"]);
365 }
366}