diff --git a/examples/android/.gitignore b/examples/android/.gitignore new file mode 100644 index 0000000..d74eb91 --- /dev/null +++ b/examples/android/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/runtime_libs diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml new file mode 100644 index 0000000..80e43ad --- /dev/null +++ b/examples/android/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "bevy_openxr_android" +version = "0.1.0" +edition = "2021" +description = "Example for building an Android OpenXR app with Bevy" +publish = false +license = "MIT OR Apache-2.0" + +[lib] +name = "bevy_openxr_android" +crate-type = ["staticlib", "cdylib"] + +[package.metadata.android] +package = "org.bevyengine.example_openxr_android" +build_targets = ["aarch64-linux-android"] +runtime_libs = "runtime_libs" +apk_name = "bevyopenxr" +# assets = "assets" +# res = "assets/android-res" +icon = "@mipmap/ic_launcher" +label = "Bevy Openxr Android" +strip = "strip" + +[package.metadata.android.sdk] +target_sdk_version = 32 + +# [package.metadata.android.application] +# icon = "@mipmap/ic_launcher" +# label = "Bevy Example" + +[dependencies] +bevy_openxr = { path = "../..", default-features = false } +bevy = { git = "https://github.com/bevyengine/bevy.git" } +openxr = { git = "https://github.com/Ralith/openxrs", features = ["mint"] } + +[profile.release] +lto = "fat" +codegen-units = 1 +panic = "abort" + +[package.metadata.android.application.activity] +theme = "@android:style/Theme.Black.NoTitleBar.Fullscreen" +config_changes = "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode" +launch_mode = "singleTask" +orientation = "landscape" +resizeable_activity = false + +[[package.metadata.android.application.activity.intent_filter]] +actions = ["android.intent.action.MAIN"] +categories = [ + "com.oculus.intent.category.VR", + "android.intent.category.LAUNCHER", +] + +# !! IMPORTANT !! +# +# When creating your own apps, make sure to generate your own keystore, rather than using our example one! +# You can use `keytool` like so: +# keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000 +# +# For more information on key signing and why it's so important, check out this article: +# https://developer.android.com/studio/publish/app-signing +# +# !! IMPORTANT !! +[package.metadata.android.signing.release] +path = "./hotham_examples.keystore" +keystore_password = "chomsky-vigilant-spa" diff --git a/examples/android/README.md b/examples/android/README.md new file mode 100644 index 0000000..801a9ad --- /dev/null +++ b/examples/android/README.md @@ -0,0 +1,46 @@ +# Bevy OpenXR Android example + +## Setup +Get libopenxr_loader.so from the Oculus OpenXR Mobile SDK and add it to `examples/android/runtime_libs/arm64-v8a` +https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/ +`examples/android/runtime_libs/arm64-v8a/libopenxr_loader.so` + +Running on Meta Quest can be done with https://github.com/rust-mobile/cargo-apk. +```sh +cargo apk run --release +``` + +## Notes + +### Relase mode +More optimisations enabled in Cargo.toml for the release mode. +This gives more performance but longer build time. +```toml +[profile.release] +lto = "fat" +codegen-units = 1 +panic = "abort" +``` + +### Cargo apk +If you see error like `Error: String `` is not a PID`, try to install cargo apk with a fix in branch. +```sh +cargo install --git https://github.com/rust-mobile/cargo-apk --branch=adb-logcat-uid +``` + +### Temporary JNIEnv log +This message is logged every frame. It's not yet fixed. +```sh +I JniUtils-inl: Creating temporary JNIEnv. This is a heavy operation and should be infrequent. To optimize, use JNI AttachCurrentThread on calling threa +``` + +### Android keystore +Release mode requires keystore. See Cargo.toml `package.metadata.android.signing.release`. + +When creating your own apps, make sure to generate your own keystore, rather than using our example one! +You can use `keytool` like so: +```sh +keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000 +``` +For more information on key signing and why it's so important, check out this article: +https://developer.android.com/studio/publish/app-signing \ No newline at end of file diff --git a/examples/android/hotham_examples.keystore b/examples/android/hotham_examples.keystore new file mode 100644 index 0000000..62623c4 Binary files /dev/null and b/examples/android/hotham_examples.keystore differ diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs new file mode 100644 index 0000000..d5501df --- /dev/null +++ b/examples/android/src/lib.rs @@ -0,0 +1,83 @@ +use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; +use bevy::prelude::*; +use bevy::transform::components::Transform; +use bevy_openxr::xr_input::debug_gizmos::OpenXrDebugRenderer; +use bevy_openxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig}; +use bevy_openxr::xr_input::trackers::{ + OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker, +}; +use bevy_openxr::DefaultXrPlugins; + +#[bevy_main] +fn main() { + App::new() + .add_plugins(DefaultXrPlugins) + .add_plugins(OpenXrDebugRenderer) + .add_plugins(LogDiagnosticsPlugin::default()) + .add_plugins(FrameTimeDiagnosticsPlugin) + .add_systems(Startup, setup) + .add_systems(Update, proto_locomotion) + .add_systems(Startup, spawn_controllers_example) + .insert_resource(PrototypeLocomotionConfig::default()) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // plane + commands.spawn(PbrBundle { + mesh: meshes.add(shape::Plane::from_size(5.0).into()), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }); + // cube + commands.spawn(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); + // cube + commands.spawn(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), + material: materials.add(Color::rgb(0.8, 0.0, 0.0).into()), + transform: Transform::from_xyz(0.0, 0.5, 1.0), + ..default() + }); + // light + commands.spawn(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + // camera + // commands.spawn((Camera3dBundle { + // transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + // ..default() + // },)); +} + +fn spawn_controllers_example(mut commands: Commands) { + //left hand + commands.spawn(( + OpenXRLeftController, + OpenXRController, + OpenXRTracker, + SpatialBundle::default(), + )); + //right hand + commands.spawn(( + OpenXRRightController, + OpenXRController, + OpenXRTracker, + SpatialBundle::default(), + )); +} diff --git a/src/graphics/vulkan.rs b/src/graphics/vulkan.rs index 61d1b52..115b583 100644 --- a/src/graphics/vulkan.rs +++ b/src/graphics/vulkan.rs @@ -42,6 +42,9 @@ pub fn initialize_xr_graphics( let xr_entry = super::xr_entry(); + #[cfg(target_os = "android")] + xr_entry.initialize_android_loader().unwrap(); + let available_extensions = xr_entry.enumerate_extensions()?; assert!(available_extensions.khr_vulkan_enable2); info!("available xr exts: {:#?}", available_extensions); @@ -82,8 +85,16 @@ pub fn initialize_xr_graphics( let blend_mode = xr_instance.enumerate_environment_blend_modes(xr_system_id, VIEW_TYPE)?[0]; + #[cfg(not(target_os = "android"))] let vk_target_version = vk::make_api_version(0, 1, 2, 0); + #[cfg(not(target_os = "android"))] let vk_target_version_xr = xr::Version::new(1, 2, 0); + + #[cfg(target_os = "android")] + let vk_target_version = vk::make_api_version(0, 1, 1, 0); + #[cfg(target_os = "android")] + let vk_target_version_xr = xr::Version::new(1, 1, 0); + let reqs = xr_instance.graphics_requirements::(xr_system_id)?; if vk_target_version_xr < reqs.min_api_version_supported || vk_target_version_xr.major() > reqs.max_api_version_supported.major() @@ -102,6 +113,8 @@ pub fn initialize_xr_graphics( let device_extensions = vec![ ash::extensions::khr::Swapchain::name(), ash::extensions::khr::DrawIndirectCount::name(), + #[cfg(target_os = "android")] + ash::extensions::khr::TimelineSemaphore::name(), ]; info!( "creating vulkan instance with these extensions: {:#?}", diff --git a/src/lib.rs b/src/lib.rs index e5ed748..ba3c952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,10 +201,17 @@ impl PluginGroup for DefaultXrPlugins { .add_before::(OpenXrPlugin) .add_after::(OpenXrInput::new(XrControllerType::OculusTouch)) .set(WindowPlugin { + #[cfg(not(target_os = "android"))] primary_window: Some(Window { present_mode: PresentMode::AutoNoVsync, ..default() }), + #[cfg(target_os = "android")] + primary_window: None, + #[cfg(target_os = "android")] + exit_condition: bevy::window::ExitCondition::DontExit, + #[cfg(target_os = "android")] + close_when_requested: true, ..default() }) }