开发环境配置
假设你的电脑上已经安装了 Android Studio,从菜单栏打开 SDK 管理器(Tools
> SDK Manager
> Android SDK
> SDK Tools
),勾选以下 3 个选项后点击 OK
按钮确认:
- Android SDK Build-Tools
- Android SDK Command-line Tools
- NDK(Side by side)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWm6NsMo-1672380557233)(https://upload-images.jianshu.io/upload_images/1787140-1585199b2e673d7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
然后,设置如下两个系统环境变量:
export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
# 注意,此处需要替换为你电脑上安装的 NDK 的版本号
export NDK_HOME=$ANDROID_SDK_ROOT/ndk/23.1.7779620
添加安卓构建目标支持
到目前为止,Android 模拟器和虚拟设备还不支持 Vulkan 图形 API(仅支持 OpenGL ES),所以开发或调试 wgpu 程序在 Android 系统上的运行时,建议使用真机(各种云测平台的云真机也行)。
如果需要支持模拟器运行,还得加上 x86_64-linux-android
和 i686-linux-android
这两个构建目标的支持。需要注意的是,如果指定了 wgpu 项目使用 Vulkan 图形后端(Instance::new(wgpu::Backends::VULKAN)
),则在模拟内运行时会崩溃:
rustup target add aarch64-linux-android armv7-linux-androideabi
自定义窗口对象
要实现一个 wgpu 里能使用的窗口对象,就必须实现 raw-window-handle 中 raw_window_handle()
raw_display_handle()
这两个分别定义在 HasRawWindowHandle
HasRawDisplayHandle
trait 里的抽象接口。
实现 raw_display_handle()
最为简单, 只需要实例化一个空的 AndroidDisplayHandle
对象做为参数。查看 raw-window-handle 的源码就会发现,实现 raw_window_handle()
抽象接口需要用到 AndroidNdkWindowHandle 对象,此对象有一个叫 a_native_window
的字段,用来指向安卓 App 的 ANativeWindow
实例。
下面我们来一步步实现它。
先给项目添加必要的依赖:
[target.'cfg(target_os = "android")'.dependencies]
jni = "0.19"
# 星号表示不锁定特定版本,在项目构建及运行时始终保持使用最新版本
ndk-sys = "*"
raw-window-handle = "0.5"
然后定义一个 NativeWindow
结构体,它只有一个叫 a_native_window
的字段:
struct NativeWindow {
a_native_window: *mut ndk_sys::ANativeWindow,
}
impl NativeWindow {
// env 和 surface 都是安卓端传递过来的参数
fn new(env: *mut JNIEnv, surface: jobject) -> Self {
let a_native_window = unsafe {
// 获取与安卓端 surface 对象关联的 ANativeWindow,以便能通过 Rust 与之交互。
// 此函数在返回 ANativeWindow 的同时会自动将其引用计数 +1,以防止该对象在安卓端被意外释放。
ndk_sys::ANativeWindow_fromSurface(env as *mut _, surface as *mut _)
};
Self {
a_native_window }
}
}
最后给 NativeWindow
实现 raw-window-handle 抽象接口:
unsafe impl HasRawWindowHandle for NativeWindow {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AndroidNdkWindowHandle::empty();
handle.a_native_window = self.a_native_window as *mut _ as *mut c_void;
RawWindowHandle::AndroidNdk(handle)
}
}
unsafe impl HasRawDisplayHandle for NativeWindow {
fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
}
}
定义 FFI
Rust 有一个关键字 extern
(kotlin 中定义 JNI 函数时也有一个对应的关键字叫 external
, 我们接下来会用到),当需要与其他语言编写的代码进行交互时,用于创建和使用外部函数接口(FFI,Foreign Function Interface)。FFI 是一种编程语言定义函数的方式,可以让不同的 ”外部“ 编程语言调用这些函数。
在 Rust 这一端,我们通过给公开函数添加 #[no_mangle]
属性来允许安卓端调用此函数:
#[no_mangle]
#[jni_fn("name.jinleili.wgpu.RustBridge")]
pub fn createWgpuCanvas(env: *mut JNIEnv, _: JClass, surface: job