Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postponed: Experimentally render to an Android surface #99

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion android/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ maplibre = { path = "../maplibre" }
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
env_logger = "0.9"
log = "0.4.16"
ndk-glue = "0.5.0" # version is required by winit
#ndk-glue = "0.5.0" # version is required by winit
ndk-glue = { git = "https://github.com/rust-windowing/android-ndk-rs.git", rev = "7e33384" }
ndk = { git = "https://github.com/rust-windowing/android-ndk-rs.git", rev = "7e33384" }
jni = "0.19.0"
raw-window-handle = "0.4"
libc = "0.2"

[lib]
#name = "maplibre_android" Currently not supported: https://github.com/rust-windowing/android-ndk-rs/issues/136
Expand Down
17 changes: 17 additions & 0 deletions android/gradle/.idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions android/gradle/.idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions android/gradle/demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="maplibre_android"/>
</activity>
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".MainActivity" android:exported="true">

</activity>
</application>

</manifest>
32 changes: 30 additions & 2 deletions android/gradle/demo/src/main/java/com/example/demo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
package com.example.demo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import org.maplibre_rs.MapLibreRs

// Currently not used. Instead the NativeActivity referenced in AndroidManifest.xml is used.

class MainActivity : AppCompatActivity() {

var mSurfaceView1: SurfaceView? = null
var mSurfaceHolder1: SurfaceHolder? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MapLibreRs.android_main()

mSurfaceView1 = findViewById<View>(R.id.surfaceView1) as SurfaceView
mSurfaceHolder1 = mSurfaceView1!!.getHolder()

mSurfaceHolder1!!.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(p0: SurfaceHolder) {
Log.v("TAG", "surfaceCreated")
MapLibreRs.android_main(p0.surface)
}

override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
Log.v(
"TAG", "surfaceChanged format=" + p1 + ", width=" + p2 + ", height="
+ p3
)
}

override fun surfaceDestroyed(p0: SurfaceHolder) {
Log.v("TAG", "surfaceDestroyed")
}
})
}
}
3 changes: 3 additions & 0 deletions android/gradle/demo/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<SurfaceView
android:layout_width="333dp"
android:layout_height="224dp" android:id="@+id/surfaceView1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Expand Down
3 changes: 2 additions & 1 deletion android/gradle/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ android {

cargo {
module = "../../"
targets = ["arm64", "x86_64"]
// targets = ["arm64", "x86_64"]
targets = ["x86_64"]
libname = "maplibre_android"
targetDirectory = "${module}/../target"
profile = "debug"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.maplibre_rs;

import android.view.Surface;

public class MapLibreRs {
public static native void android_main();
public static native void android_main(Surface surface);

static {
System.loadLibrary("maplibre_android");
Expand Down
59 changes: 56 additions & 3 deletions android/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use jni::objects::JClass;
use crate::window::AndroidMapWindowConfig;
use jni::objects::{JClass, JObject};
use jni::JNIEnv;
use log::Level;
use maplibre::platform::http_client::ReqwestHttpClient;
Expand All @@ -7,7 +8,14 @@ use maplibre::platform::schedule_method::TokioScheduleMethod;
use maplibre::render::settings::{Backends, WgpuSettings};
use maplibre::MapBuilder;
use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use std::ffi::CString;
use ndk::native_window::NativeWindow;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::os::unix::io::{FromRawFd, RawFd};
use std::thread;

mod window;

#[cfg(not(target_os = "android"))]
compile_error!("android works only on android.");
Expand All @@ -33,8 +41,53 @@ pub fn android_main() {
}

#[no_mangle]
pub extern "system" fn Java_org_maplibre_1rs_MapLibreRs_android_1main(env: JNIEnv, class: JClass) {
pub extern "system" fn Java_org_maplibre_1rs_MapLibreRs_android_1main(
env: JNIEnv,
class: JClass,
surface: JObject,
) {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));

let tag = CString::new("maplibre").unwrap();
let message = CString::new("maplibre WOORKING").unwrap();
ndk_glue::android_log(Level::Warn, &tag, &message);

unsafe {
let mut logpipe: [RawFd; 2] = Default::default();
libc::pipe(logpipe.as_mut_ptr());
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
libc::dup2(logpipe[1], libc::STDERR_FILENO);
thread::spawn(move || {
let tag = CStr::from_bytes_with_nul(b"MapLibreStderr\0").unwrap();
let file = File::from_raw_fd(logpipe[0]);
let mut reader = BufReader::new(file);
let mut buffer = String::new();
loop {
buffer.clear();
if let Ok(len) = reader.read_line(&mut buffer) {
if len == 0 {
break;
} else if let Ok(msg) = CString::new(buffer.clone()) {
ndk_glue::android_log(Level::Info, tag, &msg);
}
}
}
});
}
Comment on lines +57 to +76

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC rust-mobile/ndk#281, perhaps we should move this out of ndk_glue and make it generic :)

Perhaps some unification with the android_logger crate too.


run_multithreaded(async {
let mut map = MapBuilder::new()
.with_map_window_config(AndroidMapWindowConfig::new(env, surface))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
})
.build()
.initialize()
.await;
map.map_schedule_mut().late_init().await;
map.run()
})
}
107 changes: 107 additions & 0 deletions android/src/window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use jni::objects::JObject;
use jni::JNIEnv;
use maplibre::error::Error;
use maplibre::io::scheduler::ScheduleMethod;
use maplibre::io::source_client::HttpClient;
use maplibre::map_schedule::InteractiveMapSchedule;
use ndk::native_window::NativeWindow;
use raw_window_handle::{AndroidNdkHandle, RawWindowHandle};
use std::marker::PhantomData;
use std::thread::sleep;
use std::time::Duration;

use maplibre::window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};

pub struct AndroidNativeWindow {
window: NativeWindow,
}

pub struct AndroidMapWindowConfig<'a> {
env: JNIEnv<'a>,
surface: JObject<'a>,
}

unsafe impl raw_window_handle::HasRawWindowHandle for AndroidNativeWindow {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AndroidNdkHandle::empty();
handle.a_native_window = unsafe { self.window.ptr().as_mut() as *mut _ as *mut _ };
RawWindowHandle::AndroidNdk(handle)
}
}
Comment on lines +24 to +30

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let me add the bindings for this one directly on ndk::NativeWindow 🎉

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rust-mobile/ndk#274 Hopefully this simplifies things a bit for you, too :)


impl<'a> AndroidMapWindowConfig<'a> {
pub fn new(env: JNIEnv<'a>, surface: JObject<'a>) -> Self {
Self { env, surface }
}
}

impl<'a> MapWindowConfig for AndroidMapWindowConfig<'a> {
type MapWindow = AndroidMapWindow<'a>;

fn create(&self) -> Self::MapWindow {
let window = unsafe {
NativeWindow::from_surface(self.env.get_native_interface(), self.surface.into_inner())
}
.unwrap();

Self::MapWindow {
window: AndroidNativeWindow { window },
phantom: Default::default(),
}
}
}

pub struct AndroidMapWindow<'a> {
window: AndroidNativeWindow,
phantom: PhantomData<&'a u32>,
}

impl AndroidMapWindow<'_> {
pub fn take_event_loop(&mut self) -> Option<()> {
Some(())
}
}

impl<'a, MWC, SM, HC> EventLoop<MWC, SM, HC> for AndroidMapWindow<'a>
where
MWC: MapWindowConfig<MapWindow = AndroidMapWindow<'a>>,
SM: ScheduleMethod,
HC: HttpClient,
{
fn run(
mut self,
mut map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
max_frames: Option<u64>,
) {
for i in 0..100 {
map_schedule.update_and_redraw();
sleep(Duration::from_millis(16))
}

match map_schedule.update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
}
e => eprintln!("{:?}", e),
};

let size = self.size();
map_schedule.resize(size.width(), size.height()); // FIXME: Resumed is also called when the app launches for the first time. Instead of first using a "fake" inner_size() in State::new we should initialize with a proper size from the beginning
map_schedule.resume(&self);
}
}

impl<'a> MapWindow for AndroidMapWindow<'a> {
fn size(&self) -> WindowSize {
WindowSize::new(100, 100).unwrap()
}
}

impl<'a> HeadedMapWindow for AndroidMapWindow<'a> {
type RawWindow = AndroidNativeWindow;

fn inner(&self) -> &Self::RawWindow {
&self.window
}
}
2 changes: 1 addition & 1 deletion maplibre-winit/src/winit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl WinitMapWindow {
///* Input (Mouse/Keyboard)
///* Platform Events like suspend/resume
///* Render a new frame
impl<MWC, SM, HC> EventLoop<MWC, SM, HC> for WinitMapWindow
impl<MWC: 'static, SM, HC> EventLoop<MWC, SM, HC> for WinitMapWindow
where
MWC: MapWindowConfig<MapWindow = WinitMapWindow>,
SM: ScheduleMethod,
Expand Down
2 changes: 1 addition & 1 deletion maplibre/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub trait HeadedMapWindow: MapWindow {

/// A configuration for a window which determines the corresponding implementation of a
/// [`MapWindow`] and is able to create it.
pub trait MapWindowConfig: 'static {
pub trait MapWindowConfig {
type MapWindow: MapWindow;

fn create(&self) -> Self::MapWindow;
Expand Down