Quest pro で Native 開発をするべく OpenXR しよう。
既存のプロジェクトを改造ビルドして参考にさせてもらうことで、プロジェクト構成などを理解する。
gradle と cmake の使いこなしをある程度マスターしないと、
ビルドではまる。
NativeActivity + openxr のプロジェクトをひたすらビルドしていく。
開発環境
- Windows11(64bit)
AndroidStudio-2021.3.1
-
OpenXRMobileSDK-0.46
(Quest pro 発売時の最新) - VR実機: 初代Quest, QuestPro で実験
OpenXR とは
OpenGL のデスクトップアプリの glfw の VR 版のような感じです。
HMD Tracking や Controller Tracking やボタン操作を入力し、HMD に対して描画するための swapchain を提供するのがコア機能です。
ライブラリとしては openxr_loader
です。
https://github.com/KhronosGroup/OpenXR-SDK-Source
で提供されています。
仕様
12. List of Current Extensions
に拡張が列挙されています。
- XR_EXT_hand_tracking
- XR_FB_passthrough
- XR_FB_spatial_entity
- XR_FB_scene
等は既に記載があります。
一方で、QuestPro
で新規に導入された、
- XR_FB_body_tracking
- XR_FB_face_tracking
- XR_FB_eye_tracking_sotial
などは、まだ書いていない。
Android Studio を中心とした開発環境
C++の開発に VSCode などの他のエディタを活用する場合でも、関連するツールのインストール、バージョン合わせに有用なので AndroidStudio をインストールするとよい。
Java
AndroidStudio のインストールフォルダに付属するのでこれを使う。
JAVA_HOME=ANDROID_STUDIO_INSTALL\jre
AndroidSdk
APPDATA下にインストールされる。
ANDROID_HOME=%USERPROFILE%\AppData\Local\Android\Sdk
ANDROID_HOME/platform-tools
に adb.exe
が入っている。
CMake
ANDROID_HOME下の cmake\3.18.1
などにインストールされる。
cmake.exe
と同じパスに ninja.exe
があるのでパスを通すと vscode
などで便利。
紆余曲折を経て、ANDROID_HOME/cmake/3.22.1
を使ってます。
path の通った cmake に注意
システムに他の cmake が入っている場合はなるべくアンインストールする。
最低でも PATH が通っていない状態にする。
意図しない違うバージョンの cmake が使われたり、 cmake が見つからないエラーが起きる場合がある。
例えば pip install cmake
で C:/Python310/Scripts/cmake.exe
にPATHを通して使っていたのだが、これは gradle 運用で cmake が見つからないというトラブルが起きた。
gradle のデフォルトは cmake-3.10
https://developer.android.com/studio/projects/install-ndk?hl=ja#default-version
android {
defaultConfig {
cmake {
// ここではない
}
}
externalNativeBuild {
cmake {
version "3.22.1" // 👈 こっち
path "CMakeLists.txt"
}
}
}
cmake のバグ?らしく CMake で複数の so を生成すると、
最初の一つ以外が消えてしまう挙動に遭遇。CMake-3.22.1
にしたらなおった。
どうしても version 指定がうまくいかない場合は、
local.properties などに下のように書く方法がある。
cmake.dir = "PATH_TO_CMAKE_3_22_1"
NDK
ANDROID_HOME/ndk/21.4.7075529
などにインストールされる。
追加インストール
Python
OpenXR-SDK の CMake が使っている。
gradle-7.4
デフォルトの CMake-3.10
から find できる一番新しいバージョンである Python-3.7
をインストールする。
OpenXR-SDK の cmake で使われている
find_package(PythonInterp 3)
が失敗する。
cmake のバージョンによって発見できる Python のバージョンに限界があります。
cmake | python |
---|---|
3.10 | 3.7 |
3.18 | 3.9 |
3.22 | 3.10 |
CMAKE_VERSION\share\cmake-VERSION\Modules\FindPython\Support.cmake
などを見ると
対応している python がわかります。
Gradle
コマンドラインで使う場合は追加でダウンロードしてパスを通す。
gradlew を使うことが多いので必要ないことが多い
ANDROID_STUDIO と同じ gradle-7.4
を選ぶ。
ANDROID_STUDIO_INSTALL\plugins\gradle
にライブラリは入っているがコマンドラインが無い。
gradle-7.4 で AGP(AndroidGradlePlugin)-7.3.1 を使う
gradlew で gradlew を更新できます。
> cd src/tests/hello_xr
src/tests/hello_xr> .\gradlew --version
7.0.2
src/tests/hello_xr> .\gradlew wrapper --gradle-version 7.4
src/tests/hello_xr> .\gradlew --version
7.4
AGP のバージョン指定は rootProject の最初の方です。
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.3.1" // 👈 AGP のバージョン指定
}
}
adb によるデバイス接続確認
ANDROID_HOME/platform-tools/adb.exe
です。
- スマホのQuestアプリでQuestを開発者モードにする
- Windows 側で QUEST の usb ドライバをインストールする。 https://developer.oculus.com/downloads/package/oculus-adb-drivers
- Quest 実機を PCに USB 接続する
- Quest 側で USB 接続を許可する
# 接続されたデバイスを一覧する
$ adb devices -l
NDK練習
最初に、ndk-samples から native_activity をやります。
native_activy は java 無しで CMake を使う小さいサンプルで、300行くらいの c++ で OpenGLES で glClear
するだけという手頃なプロジェクトです。
gradle と CMake の構成もシンプルでわかりやすいです。
AndroidStudio で open
native_activity を開く。
ビルドしてデプロイする。
app/build.gradle の構成
抜粋。
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
ndkVersion '22.1.7171670'
defaultConfig {
applicationId = 'com.example.native_activity'
minSdkVersion 14
targetSdkVersion 28
externalNativeBuild { // 👈 defaultConfig.externalNativeBuild。artuments などが指定できる。cmake 発動しない
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
}
externalNativeBuild { // 👈 defaultConfig.externalNativeBuild と別物。cmake で指定できる内容が違う
cmake {
version '3.18.1'
path 'src/main/cpp/CMakeLists.txt' // 👈 必須ぽい?これが書いてあると cmake が発動する。
}
}
}
CMake の構成
main.cpp 単体で so ファイルを作成するシンプルなプロジェクトです。
cmake_minimum_required(VERSION 3.4.1)
# build native_app_glue as a static lib
set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS}")
add_library(native_app_glue STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) # 👈 NDK で必要
# now build app's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Werror")
# Export ANativeActivity_onCreate(),
# Refer to: https://github.com/android-ndk/ndk/issues/381.
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") # 👈 NDK で必要
add_library(native-activity SHARED main.cpp) # 👈 apk の lib に libnative-activity.so を含める
target_include_directories(native-activity PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue)
# add lib dependencies
target_link_libraries(native-activity
android # 👈 システムの so にリンク。apk に含まない
native_app_glue # android_native_app_glue.c
EGL # 👈 システムの so にリンク。apk に含まない
GLESv1_CM # 👈 システムの so にリンク。apk に含まない
log # 👈 システムの so にリンク。apk に含まない
)
gradle からの cmake 呼び出し
エラーメッセージから得た。
下記のような感じで呼び出しています。
CMAKE_EXE
-HPROJECT_DIR
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_SYSTEM_VERSION=24
-DANDROID_PLATFORM=android-24
-DANDROID_ABI=arm64-v8a
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a
-DANDROID_NDK=ANDROID_HOME\\ndk\\21.4.7075529
-DCMAKE_ANDROID_NDK=ANDROID_HOME\\ndk\\21.4.7075529
-DCMAKE_TOOLCHAIN_FILE=NDK_DIR\\build\\cmake\\android.toolchain.cmake
-DCMAKE_MAKE_PROGRAM=ANDROID_HOME\\cmake\\3.18.1\\bin\\ninja.exe
-DCMAKE_CXX_FLAGS=GRDLE_EXTERNAL_NATIVE_BUILD_CMAKE_CPPFLAGS
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=PROJECT_DIR\\build\\intermediates\\cxx\\Debug\\__TMP__\\obj\\arm64-v8a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=PROJECT_DIR\\build\\intermediates\\cxx\\Debug\\__TMP__\\obj\\arm64-v8a
-DCMAKE_BUILD_TYPE=Debug
-BPROJECT_DIR\\.cxx\\Debug\\__TMP__\\arm64-v8a
-GNinja
ビルド結果を apk に送り込む
gradle から cmake を呼び出したときに CMAKE_LIBRARY_OUTPUT_DIRECTORY
が指定されてそこに so
が出力されます。
native-activity\app\build\intermediates\cxx\Debug\RANDOM\obj
x86_64\libnative-activity.so
arm64-v8a\libnative-activity.so
x86\libnative-activity.so
armeabi-v7a\libnative-activity.so
abi 毎に4回ビルドされる。
apk の構成
app\build\outputs\apk
に出力されます。
zip アーカイブなので中身の lib フォルダを見ると下記のようになっています。
AndroidStudio の ApkAnalyzer で見ることができます。
native-activity\app\build\outputs\apk\debug\app-debug.apk
lib/arm64-v8a/libnative-activity.so
lib/x86_64/libnative-activity.so
lib/armeabi-v7a/libnative-activity.so
lib/x86/libnative-activity.so
要するに
add_library(LIB_NAME SHARED some.cpp)
libLIB_NAME.so
が apk に含まれます。
OpenXR-SDK
OpenXR-SDK-Source から hello_xr をやります。
デスクトップは cmake でビルドするだけなので簡単です。
OpenXR-SDK と OpenXR-SDK-Source
OpenXR-SDK-Source には openxr.h
が含まれておらず、ビルドすることで生成することができます。
生成物をコミットしたのが OpenXR-SDK です。
openxr_loader.dll
などをビルドしたのが OpenXR.Loader.1.0.25.nupkg
です。
Windows 環境では openxr_loader が複数の RunTime のどれかをロードするという挙動をするので、
openxr_loader 自体は自前ビルドでよいようです。
Android の openxr_loader はそのデバイスのランタイムをロードする専用の libopenxr_loader.so
を、
デバイスのベンダーが配布するという方式のようです。
libopenxr_loader.so は OpenXRMobileSDK に含まれるものを使う
OVR_MobileSdk の中に入っています。
OpenXRMobileSDK\OpenXR\Libs\Android\armeabi-v7a\Debug\libopenxr_loader.so
OpenXRMobileSDK\OpenXR\Libs\Android\armeabi-v7a\Release\libopenxr_loader.so
OpenXRMobileSDK\OpenXR\Libs\Android\arm64-v8a\Debug\libopenxr_loader.so
OpenXRMobileSDK\OpenXR\Libs\Android\arm64-v8a\Release\libopenxr_loader.so
Android の libopenxr_loader.so の自前ビルドは (たぶん) 動かない
OpenXR-SDK もしくは OpenXR-SDK-Source からビルドすることができます。
試しに hello_xr で使ってみたところ
真っ黒になりました。
Quest固有の機能を提供できないので何もしないローダーになっていそう。
OpenXR-SDK-Source の src/tests/hello_xr を Quest 向けにビルドする
native_activiy に、openxr_loader が追加された VR のプロジェクトです。
OpenXR の拡張機能を使わない、HMD Tracking, コントローラ Tracking と入力、VR レンダリング の入ったプロジェクトです。
d3d, opengl, vulkan, windows, android 全対応のため CMake の構成が複雑化しています。
AndroidStudio で hello_xr を開く
AndroidStudio で OpenXR-SDK-Source の src/tests/hello_xr
フォルダを開きます。
修正してビルドする
Quest 向けに hello_xr を改造する手順が下記の URL に書いてあります。
https://developer.oculus.com/documentation/native/android/mobile-build-run-hello-xr-app/
- [cmake]openxr_loader をビルドせずに OpenXRMobileSDK の物を使うようにする
- [gladle]cmake 引数を変更
- [AndroidManifest.xml]intent を quest 用に変更
ビルド済みのライブラリを apk に含める
add_library(openxr_loader SHARED IMPORTED) # 👈 SHARED IMPORTED でできます
get_filename_component(
MOBILE_SDK_DIR
${CMAKE_CURRENT_LIST_DIR}/../../OpenXRMobileSDK ABSOLUTE) # 👈 パスを文字列で使うと失敗するときがあったので、絶対パスで回避
set_property(
TARGET
openxr_loader
PROPERTY
IMPORTED_LOCATION # 👈 IMPORTED_LOCATION でパスを指定します
${MOBILE_SDK_DIR}/OpenXR/Libs/Android/${ANDROID_ABI}/release/libopenxr_loader.so
)
APK の構成
hello_xr の apk には以下の so が含まれます。
armeabi-v7a\libXrApiLayer_core_validation.so
armeabi-v7a\libXrApiLayer_api_dump.so
armeabi-v7a\libopenxr_loader.so
armeabi-v7a\libhello_xr.so
armeabi-v7a\libGLESv3.so 👈 たぶん不要
armeabi-v7a\libEGL.so 👈 たぶん不要
arm64-v8a\libXrApiLayer_core_validation.so
arm64-v8a\libXrApiLayer_api_dump.so
arm64-v8a\libopenxr_loader.so
arm64-v8a\libhello_xr.so
arm64-v8a\libGLESv3.so 👈 たぶん不要
arm64-v8a\libEGL.so 👈 たぶん不要
OpenXRMobileSDK のサンプル
v0.46
OVR_MobileSdk から XrHandsFB をやります。
native_activiy に、openxr_loader と XR_EXT_hand_tracking が追加された VR のプロジェクトです。
- 中規模の C++ フレームワークが ndk-build(Makefile) で記述されている
- gradle の構成が変則的で、python スクリプト
OpenXRMobileSDK/XrSamples/XrHandsFB/Projects/Android/build.py
から gradle を実行するためわかりにくい-
OpenXRMobileSDK/build.gradle
+OpenXRMobileSDK/XrSamples/XrHandsFB/Projects/Android/settings.gradle
の組み合わせで動くトリックになっている様子 - そのため AndroidStudio で開けない
-
XrSamples/XrHandsFB
XrSamples は ndk-build を使う方式記述されているので、以下の Makefile
に C++ の構成が記述されている。
XrSamples\XrHandsFB\Projects\Android\jni\Android.mk
- OpenXR\Projects\AndroidPrebuilt\jni\Android.mk
- SampleXrFramework\Projects\Android\jni\Android.mk
- SampleCommon\Projects\Android\jni\Android.mk
- 3rdParty\khronos\ktx\makefiles\androidprebuilt\jni\Android.mk
- 3rdParty\khronos\ktx\makefiles\android\jni\Android.mk
OpenXRMobileSDK/XrSamples/XrHandsFB/Projects/Android/build.py
を実行する。
=> OpenXRMobileSDK\XrSamples\XrHandsFB\Projects\Android\build\outputs\apk\release\xrhandsfb-release.apk
OpenXRMobileSDK/bin/scripts/ovrbuild.py
を呼び出していて、
ovrbuild
は、環境変数 ANDROID_HOME
と ANDROID_NDK_HOME
を必要としている。
build.py
実行前に追加の環境変数 ANDROID_NDK_HOME = $ANDROID_HOME/ndk/25.1.8937393
のように設定します。
build.gradle と CMake を書いてみた
AndroidStudio で開けるようになった。
首尾よく Quest で動きました。
OpenXR のプロジェクト最小構成はだいたい以下の通り。
- libopenxr_loader.so を含む(OpenXRMobileSDK に含まれるものを使う)
-
openxr/openxr.h
は OpenXR-SDK を使う -
openxr/openxr.h
に含まれていない新しい拡張のヘッダは OpenXRMobileSDK に入っている。
-
- NativeActivity で Java 抜き
- NativeActivity を実装する so を含む(自作する)
- OpenGLES で描画する
- Quest 専用の intent
com.oculus.intent.category.VR
が必要
VSCode で C++ の開発をする
gradle からの CMake の呼び出しが
CMAKE_EXE
-HPROJECT_DIR
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_SYSTEM_VERSION=24
-DANDROID_PLATFORM=android-24
-DANDROID_ABI=arm64-v8a
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a
-DANDROID_NDK=ANDROID_HOME\\ndk\\21.4.7075529
-DCMAKE_ANDROID_NDK=ANDROID_HOME\\ndk\\21.4.7075529
-DCMAKE_TOOLCHAIN_FILE=NDK_DIR\\build\\cmake\\android.toolchain.cmake
-DCMAKE_MAKE_PROGRAM=ANDROID_HOME\\cmake\\3.18.1\\bin\\ninja.exe
-DCMAKE_CXX_FLAGS=GRDLE_EXTERNAL_NATIVE_BUILD_CMAKE_CPPFLAGS
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=PROJECT_DIR\\build\\intermediates\\cxx\\Debug\\__TMP__\\obj\\arm64-v8a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=PROJECT_DIR\\build\\intermediates\\cxx\\Debug\\__TMP__\\obj\\arm64-v8a
-DCMAKE_BUILD_TYPE=Debug
-BPROJECT_DIR\\.cxx\\Debug\\__TMP__\\arm64-v8a
-GNinja
という感じなので以下のような設定で、vscode の F7 で cmake ビルドし、
lsp として clangd を稼働させることができます。
build 結果は ${workspaceFolder}/build に出ます。
必要に応じて、gradle の android.defaultConfig.externalNativeBuild.cmake.arguments
の内容を追記します。
{
"cmake.sourceDirectory": "${workspaceFolder}/DIR_TO_CMAKELISTS_TXT",
"cmake.configureSettings": {
"CMAKE_TOOLCHAIN_FILE": "${env:ANDROID_HOME}/ndk/21.4.7075529/build/cmake/android.toolchain.cmake",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"ANDROID": "ON",
"ANDROID_PLATFORM": "android-28",
"ANDROID_ABI": "arm64-v8a",
"CMAKE_ANDROID_ARCH_ABI": "arm64-v8a",
},
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/build" // 👈 GRADLE_PROJECT\app\.cxx\Debug\RANDOM\arm64-v8a\compile_commands.json
],
}
gradle の出力する compile_commands.json
を参照させることもできるけど、
パスに乱数が含まれていて不便。
おそらく、${workspaceFolder}/build
を指定する方が便利。
LÖVR
lua で VR するフレームワーク。
まだ、ビルドして動作するところまでしか見てない。
apk 作成も含めて全部 CMake でビルドするようになっていて gradle が無い。
AndroidStudio からデバッガをアタッチしたいので gradle プロジェクトを作る。
TODO:
関連