Android (rust) vulkan (JNI) 画一个三角形: VulkanSurfaceView 初始化

上文说到, vulkan 作为一种 GPU 编程接口标准, 具有很好的跨平台能力. 并且在 wayland (GNU/Linux) 成功使用 vulkan 绘制了一个三角形.

今天, 我们同样使用 vulkano (rust), 在 Android (手机) 也画一个三角形吧 ~

本文的解决方案主要参考了 stackoverflow 的一篇文章 (链接见下面的 参考资料), 在此表示感谢 !

注意, 本文中的代码写的比较丑, 只是用来快速验证工作原理, 没有经过充分的优化, 很多地方不完善, 仅供参考.

这里是 穷人小水滴, 专注于 穷人友好型 低成本技术. (本文为 62 号作品. )


相关文章:

  • 《rust GTK4 窗口创建与 wayland Subsurface (vulkan 渲染窗口初始化 (Linux) 上篇)》 https://blog.csdn.net/secext2022/article/details/142300776
  • 《vulkano (rust) 画一个三角形 (vulkan 渲染窗口初始化 (Linux) 下篇)》 https://blog.csdn.net/secext2022/article/details/142300877

参考资料:

  • https://stackoverflow.com/questions/45157950/can-we-use-vulkan-with-java-activity-on-android-platform
  • https://stuff.mit.edu/afs/sipb/project/android/docs/reference/android/opengl/GLSurfaceView.html
  • https://stuff.mit.edu/afs/sipb/project/android/docs/reference/android/app/NativeActivity.html
  • https://crates.io/crates/jni
  • https://crates.io/crates/ndk
  • https://crates.io/crates/android_logger
  • https://crates.io/crates/raw-window-handle

目录

  • 1 Android (kotlin) 部分
    • 1.1 MainActivity
    • 1.2 VulkanSurfaceView
    • 1.3 JNI
  • 2 rust 代码部分
    • 2.1 JNI
    • 2.2 RawWindowHandle
    • 2.3 vulkan 渲染测试
    • 2.4 rust 部分的完整代码
  • 3 编译测试
    • 3.1 编译 rust (libpmse_apk.so)
    • 3.2 编译 apk (gradle)
    • 3.3 测试运行
  • 4 总结与展望

1 Android (kotlin) 部分

在 Android 应用 (apk) 之中, 如果想要直接使用 GPU 渲染 (OpenGL/vulkan), 官方文档有这两种方式 (链接见上面的 参考资料):

  • GLSurfaceView: 这个是 JVM (Java) 代码, 但是只能使用 OpenGL 进行渲染.

  • NativeActivity: 这个支持使用 vulkan, 但是这种方式要求, 整个 Activity (可以理解为 窗口) 都要使用 native 代码 (比如 C/C++) 来编写.


插播小知识: native 代码和 JVM 字节码.

在 Android 系统开发应用软件, 可以使用很多种编程语言, 但是基本上可以分为两大类: JVM 和 native.

其中 JVM 就是编译成 JVM 字节码 (dex), 然后由 JVM 虚拟机 (ART) 解释运行 (或者 JIT/AOT 等编译) 的编程语言, 常见的有古老的 Java 和新的 kotlin (JVM 还有很多种别的编程语言, 比如 Scala).

native 就是直接编译成 CPU 可以执行的二进制指令代码, 比如 ARM (arm64-v8a) 或者 x86_64, 然后可以由 CPU 直接执行 (无需 JVM 这种虚拟机). native 编程语言有 C/C++, 当然 rust 这种编译到 LLVM 的编程语言也是 native 的.

Android 操作系统使用 Linux 内核, 所以 native 代码编译成动态链接库 (文件名后缀 .so, 文件格式 ELF), 然后加载到运行的应用进程中.

一般来说, JVM 语言编写起来更方便更容易, 能够更好的使用 Android 系统提供的功能 (调用 Android API). native 语言性能更高 (运行速度快, 占用内存小), 但是编写起来难度较大.


一个应用软件, 特别是 跨平台 的应用, 代码可以分成两大部分: 平台无关 代码, 平台相关 代码. 平台无关就是, 功能 (代码) 和具体的平台 (操作系统/设备形态等) 联系不太紧密, 一套代码可以到处运行. 平台相关就是, 这些代码和特定的平台联系密切, 一般只能用于这个平台, 别的地方用不了.

上述两种在 Android 直接使用 GPU 的方式中, GLSurfaceView 只能使用 OpenGL, 但是窝想使用 vulkan, 排除. NativeActivity 必须全部使用 native 代码, 但是窝想使用 kotlin 编写 Android 平台相关代码, 使用 rust 编写平台无关代码, 也排除.

为什么不使用 rust 编写 Android 平台相关代码呢 ? 并不是技术上无法实现, 而是 rust 代码写起来确实比较费劲 (虽然代码质量也更高), 并且平台相关代码在别的地方也用不到, 性价比较低. 如果使用 kotlin 编写 Android 平台相关代码, 就会更方便更容易, 也能更好的使用 Android 系统 API, 提供更好的用户体验.

所以, 上述两种官方文档中的方式都不行, 我们需要新的创造性的方式, 在 Android 使用 vulkan. 主要思路是, 整个应用 (以及 Activity) 仍然使用 kotlin 编写, rust 代码只负责 vulkan 渲染部分 (也就是绘制界面区域的一部分). 或者说 rust 代码 嵌入 (embed) 一个普通的 Android apk 之中.

1.1 MainActivity

使用 AndroidStudio 创建一个普通的空白应用, 然后修改源代码文件 app/src/main/java/io/github/fm_elpac/pmse_apk/MainActivity.kt:

package io.github.fm_elpac.pmse_apkimport android.os.Bundle
import android.app.Activityimport io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanSurfaceViewclass MainActivity : Activity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var v = VulkanSurfaceView(this)setContentView(v)}
}

这是一个简单的普通 Activity (可以理解为窗口/主界面, 在应用启动后显示). 其中使用 setContentView() 函数设置了界面显示的内容, VulkanSurfaceView 就是使用 vulkan 渲染的部分 (相当于一块画布).

1.2 VulkanSurfaceView

文件 app/src/main/java/io/github/fm_elpac/pmse_apk/vulkan_bridge/VulkanSurfaceView.kt:

package io.github.fm_elpac.pmse_apk.vulkan_bridgeimport android.content.Context
import android.util.AttributeSet
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceViewclass VulkanSurfaceView: SurfaceView, SurfaceHolder.Callback2 {private var b = VulkanJNI()// constructor just call superconstructor(context: Context): super(context) {}constructor(context: Context, attrs: AttributeSet): super(context, attrs) {}constructor(context: Context, attrs: AttributeSet, defStyle: Int): super(context, attrs, defStyle) {}constructor(context: Context, attrs: AttributeSet, defStyle: Int, defStyleRes: Int): super(context, attrs, defStyle, defStyleRes) {}init {alpha = 1Fholder.addCallback(this)}// TODO GLSurfaceViewoverride fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {b.resize(width, height)}override fun surfaceDestroyed(holder: SurfaceHolder) {b.destroy()}override fun surfaceCreated(holder: SurfaceHolder) {holder.let { h ->b.create(h.surface)}}override fun surfaceRedrawNeeded(holder: SurfaceHolder) {b.draw()}
}

此处创建一个类 VulkanSurfaceView, 继承 SurfaceView, 实现 SurfaceHolder.Callback2 回调接口.

其中 SurfaceView 是 Android 应用界面的一块 “画布”, 可以使用 OpenGL/vulkan 绘制. SurfaceView 继承 View, 也就是 Android 应用界面的一个组件. SurfaceHolderSurfaceView 正常工作所需要的.

其中几个重要的回调函数:

  • surfaceCreated: 这块画布创建后回调, 此时做一些初始化的工作.

  • surfaceChanged: 当画布发生变化时回调, 比如格式, 宽高 (像素) 改变.

  • surfaceRedrawNeeded: 需要重新绘制时回调.

  • surfaceDestroyed: 画布销毁时回调.

注意: SurfaceView 支持在另一个单独的线程中进行渲染, 这样可以提高性能, 不再阻塞 UI 主线程, 也是推荐的使用方式. 但是此处为了方便, 直接在 UI 线程中进行了渲染.

1.3 JNI

JVM 想要调用 native 代码, 就要使用大名鼎鼎的 JNI (Java Native Interface).

文件 app/src/main/java/io/github/fm_elpac/pmse_apk/vulkan_bridge/VulkanJNI.kt:

package io.github.fm_elpac.pmse_apk.vulkan_bridgeimport android.view.Surfaceclass VulkanJNI {init {System.loadLibrary("pmse_apk")// debugprintln("DEBUG: load libpmse_apk.so")}private external fun nativeInit()private external fun nativeCreate(surface: Surface)private external fun nativeDestroy()private external fun nativeResize(width: Int, height: Int)private external fun nativeDraw()constructor() {nativeInit()}fun create(surface: Surface) {// debugprintln("DEBUG: before nativeCreate()")nativeCreate(surface)}fun destroy() {// debugprintln("DEBUG: before nativeDestroy()")nativeDestroy()}fun resize(width: Int, height: Int) {// debugprintln("DEBUG: before nativeResize()")nativeResize(width, height)}fun draw() {// debugprintln("DEBUG: before nativeDraw()")nativeDraw()}
}

这是 JNI 的 JVM 侧. 首先使用 System.loadLibrary("pmse_apk") 在初始化时, 加载 libpmse_apk.so 动态链接库文件 (native 代码会编译成这个).

然后使用 external fun 声明 native 侧的函数. 然后就可以调用啦 ~

添加了一些调试输出, 方便观察程序的行为 (运行过程).

2 rust 代码部分

kotlin 部分的代码讲完了, 该轮到 rust 了.

此处我们要使用几个别人已经写好的库: jni, ndk, android_logger, raw-window-handle.

前人已经把路铺好了, 在此表示感谢 !

2.1 JNI

这是 JNI 的 native 侧.

/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeInit()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeInit<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {// init android loggerandroid_logger::init_once(Config::default().with_max_level(LevelFilter::Trace).with_tag("pmse_apk"),);debug!("from rust: nativeInit()");测试1.lock().unwrap().init();
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeCreate(Surface)
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeCreate<'local,
>(env: JNIEnv<'local>,_class: JClass<'local>,surface: JClass<'local>,
) {debug!("from rust: nativeCreate()");let nw = unsafe { ANativeWindow_fromSurface(env.get_raw(), **surface) };let h = HandleBox::new(nw);测试1.lock().unwrap().create(h);
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeDestroy()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeDestroy<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {debug!("from rust: nativeDestroy()");测试1.lock().unwrap().destroy();
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeResize(Int, Int)
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeResize<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,w: i32,h: i32,
) {debug!("from rust: nativeResize({}, {})", w, h);测试1.lock().unwrap().resize(w as u32, h as u32);
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeDraw()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeDraw<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {debug!("from rust: nativeDraw()");测试1.lock().unwrap().draw();
}

这几个函数是上面 kotlin 代码通过 JNI 直接调用到的. 使用 #[no_mangle]pub extern "system" fn 来声明导出函数, 在编译之后的 .so 文件中会原样保留函数名 (如果没有 #[no_mangle], 编译器就会修改函数名).

JNI 的一个关键重点是, 函数名称要对应, 否则会加载失败 (程序崩溃). 比如 JVM 侧的 io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeCreate 函数, 对应 native 侧的 Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeCreate 函数. 嗯, 很有 Java 遗风, 名称特别长 !

添加了一些调试输出 (Android logcat) 代码, 方便测试.

2.2 RawWindowHandle

从之前发布的文章中可知, 想要初始化 vulkan, 就要拿到原始窗口指针. 对于 Android 平台也是如此.

#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeCreate<'local,
>(env: JNIEnv<'local>,_class: JClass<'local>,surface: JClass<'local>,
) {debug!("from rust: nativeCreate()");let nw = unsafe { ANativeWindow_fromSurface(env.get_raw(), **surface) };let h = HandleBox::new(nw);测试1.lock().unwrap().create(h);

此处使用 Android NDK 提供的函数 ANativeWindow_fromSurface, 从 Surface 创建 ANativeWindow.

还记得 Surface 嘛 ? 就是 kotlin 代码通过 JNI 传过来的一块 “画布”.

/// 提供 RawWindowHandle, RawDisplayHandle (Android)
#[derive(Debug, Clone)]
pub struct HandleBox {rd: RawDisplayHandle,rw: RawWindowHandle,
}impl HandleBox {pub fn new(w: *mut ANativeWindow) -> Self {let mut h = AndroidNdkWindowHandle::empty();h.a_native_window = w as *mut _;let rw = RawWindowHandle::AndroidNdk(h);let rd = RawDisplayHandle::Android(AndroidDisplayHandle::empty());Self { rd, rw }}
}unsafe impl HasRawDisplayHandle for HandleBox {fn raw_display_handle(&self) -> RawDisplayHandle {self.rd}
}unsafe impl HasRawWindowHandle for HandleBox {fn raw_window_handle(&self) -> RawWindowHandle {self.rw}
}// TODO
unsafe impl Send for HandleBox {}
unsafe impl Sync for HandleBox {}

这是对 Android 平台的原始窗口指针的实现. 别的平台初始化 vulkan 通常需要 2 个原始指针, 显示指针 (RawDisplayHandle) 和 窗口指针 (RawWindowHandle), 但是 Android 只需要一个窗口指针, 显示指针是空的 (占位).

注意此处再次用到了不安全 (unsafe) rust.

2.3 vulkan 渲染测试

use pmse_render::{draw_t::{PmseRenderT, 三角形},PmseRenderHost, PmseRenderInit,
};struct 测试渲染 {pri: Option<PmseRenderInit>,pr: Option<PmseRenderHost>,t: Option<PmseRenderT>,
}impl 测试渲染 {pub const fn new() -> Self {Self {pri: None,pr: None,t: None,}}pub fn init(&mut self) {let pri = PmseRenderInit::vulkan().unwrap();self.pri = Some(pri);}// after init()pub fn create(&mut self, h: HandleBox) {let pr = self.pri.take().unwrap().init_w(h.into()).unwrap();self.pr = Some(pr);}pub fn destroy(&mut self) {// TODO}// after create()pub fn resize(&mut self, w: u32, h: u32) {let pr = self.pr.take().unwrap();let t = PmseRenderT::new(pr, (w, h)).unwrap();self.t = Some(t);}pub fn draw(&mut self) {self.t.as_mut().unwrap().draw(vec![三角形::default()]).unwrap();}
}// TODO 全局变量, 方便测试
static 测试1: Mutex<测试渲染> = Mutex::new(测试渲染::new());

调用之前的文章中相同的 vulkan 初始化和测试代码, 绘制一个三角形.

此处设置一个全局变量, 方便测试. 正式代码中不建议这么用.

2.4 rust 部分的完整代码

文件 pmse-apk/Cargo.toml:

[package]
name = "pmse-apk"
version = "0.1.0-a1"
edition = "2021"
license = "LGPL-3.0-or-later"[lib]
crate-type = ["cdylib"][dependencies]
log = "^0.4.22"jni = "^0.21.1"
android_logger = "^0.14.1"
ndk = "^0.9.0"
ndk-sys = "^0.6.0"# vulkano version
raw-window-handle = "0.5"pmse-render = { path = "../../pmse-render", version = "^0.1.0-a2" }[workspace]

文件 pmse-apk/src/lib.rs:

//! pmse-apk
#![deny(unsafe_code)]pub mod jni;

文件 pmse-apk/src/jni.rs:

//! Android JNI
#![allow(unsafe_code)]
use std::sync::Mutex;use android_logger::Config;
use jni::{objects::JClass, JNIEnv};
use log::{debug, LevelFilter};
use ndk_sys::{ANativeWindow, ANativeWindow_fromSurface};
use raw_window_handle::{AndroidDisplayHandle, AndroidNdkWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,RawDisplayHandle, RawWindowHandle,
};use pmse_render::{draw_t::{PmseRenderT, 三角形},PmseRenderHost, PmseRenderInit,
};struct 测试渲染 {pri: Option<PmseRenderInit>,pr: Option<PmseRenderHost>,t: Option<PmseRenderT>,
}impl 测试渲染 {pub const fn new() -> Self {Self {pri: None,pr: None,t: None,}}pub fn init(&mut self) {let pri = PmseRenderInit::vulkan().unwrap();self.pri = Some(pri);}// after init()pub fn create(&mut self, h: HandleBox) {let pr = self.pri.take().unwrap().init_w(h.into()).unwrap();self.pr = Some(pr);}pub fn destroy(&mut self) {// TODO}// after create()pub fn resize(&mut self, w: u32, h: u32) {let pr = self.pr.take().unwrap();let t = PmseRenderT::new(pr, (w, h)).unwrap();self.t = Some(t);}pub fn draw(&mut self) {self.t.as_mut().unwrap().draw(vec![三角形::default()]).unwrap();}
}// TODO 全局变量, 方便测试
static 测试1: Mutex<测试渲染> = Mutex::new(测试渲染::new());/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeInit()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeInit<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {// init android loggerandroid_logger::init_once(Config::default().with_max_level(LevelFilter::Trace).with_tag("pmse_apk"),);debug!("from rust: nativeInit()");测试1.lock().unwrap().init();
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeCreate(Surface)
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeCreate<'local,
>(env: JNIEnv<'local>,_class: JClass<'local>,surface: JClass<'local>,
) {debug!("from rust: nativeCreate()");let nw = unsafe { ANativeWindow_fromSurface(env.get_raw(), **surface) };let h = HandleBox::new(nw);测试1.lock().unwrap().create(h);
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeDestroy()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeDestroy<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {debug!("from rust: nativeDestroy()");测试1.lock().unwrap().destroy();
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeResize(Int, Int)
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeResize<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,w: i32,h: i32,
) {debug!("from rust: nativeResize({}, {})", w, h);测试1.lock().unwrap().resize(w as u32, h as u32);
}/// io.github.fm_elpac.pmse_apk.vulkan_bridge.VulkanJNI.nativeDraw()
#[no_mangle]
pub extern "system" fn Java_io_github_fm_1elpac_pmse_1apk_vulkan_1bridge_VulkanJNI_nativeDraw<'local,
>(_env: JNIEnv<'local>,_class: JClass<'local>,
) {debug!("from rust: nativeDraw()");测试1.lock().unwrap().draw();
}/// 提供 RawWindowHandle, RawDisplayHandle (Android)
#[derive(Debug, Clone)]
pub struct HandleBox {rd: RawDisplayHandle,rw: RawWindowHandle,
}impl HandleBox {pub fn new(w: *mut ANativeWindow) -> Self {let mut h = AndroidNdkWindowHandle::empty();h.a_native_window = w as *mut _;let rw = RawWindowHandle::AndroidNdk(h);let rd = RawDisplayHandle::Android(AndroidDisplayHandle::empty());Self { rd, rw }}
}unsafe impl HasRawDisplayHandle for HandleBox {fn raw_display_handle(&self) -> RawDisplayHandle {self.rd}
}unsafe impl HasRawWindowHandle for HandleBox {fn raw_window_handle(&self) -> RawWindowHandle {self.rw}
}// TODO
unsafe impl Send for HandleBox {}
unsafe impl Sync for HandleBox {}

3 编译测试

代码写好了, 接下来编译, 运行测试一下吧 ~

3.1 编译 rust (libpmse_apk.so)

编译命令如下 (操作系统 ArchLinux, 仅供参考):

> export ANDROID_NDK_HOME=/opt/android-ndk
> export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
> export CC_aarch64_linux_android=aarch64-linux-android28-clang
> export CXX_aarch64_linux_android=aarch64-linux-android28-clang++
> export AR_aarch64_linux_android=llvm-ar
> export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android28-clang
> cargo build --target aarch64-linux-androidFinished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s

编译后的文件位于 target/aarch64-linux-android/debug/libpmse_apk.so.

3.2 编译 apk (gradle)

首先, 把上面编译获得的 .so 文件放 (复制) 到这个位置:

app/src/main/jniLibs/arm64-v8a/libpmse_apk.so

然后使用如下编译命令:

env JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug

这一步使用 AndroidStudio 进行编译, 也是一样的.

编译之后获得的 apk 文件位于 app/build/outputs/apk/debug/app-debug.apk.

3.3 测试运行

使用 adb 把应用安装到手机:

> adb devices
List of devices attached
643fa0f6	device> adb install app-debug.apk
Performing Streamed Install
Success

使用 logcat 查看调试日志:

adb logcat -c
adb logcat | grep -e System.out -e pmse

在手机上打开应用:

在这里插入图片描述

同时能看到日志:

09-24 08:10:21.837  7418  7418 I System.out: DEBUG: load libpmse_apk.so
09-24 08:10:21.837  7418  7418 D pmse_apk: pmse_apk::jni: from rust: nativeInit()
09-24 08:10:21.837  7418  7418 D pmse_apk: pmse_render::vulkan::vulkan_init: init vulkan .. .
09-24 08:10:21.856  7418  7418 D vulkan  : searching for layers in '/data/app/~~vaPOKI1rvIXhWibZF5rASw==/io.github.fm_elpac.pmse_apk-VJsfFmA-8OzE7rL8tPsMeA==/lib/arm64'
09-24 08:10:21.856  7418  7418 D vulkan  : searching for layers in '/data/app/~~vaPOKI1rvIXhWibZF5rASw==/io.github.fm_elpac.pmse_apk-VJsfFmA-8OzE7rL8tPsMeA==/base.apk!/lib/arm64-v8a'09-24 08:10:21.901  7418  7418 I System.out: DEBUG: before nativeCreate()
09-24 08:10:21.902  7418  7418 D pmse_apk: pmse_apk::jni: from rust: nativeCreate()
09-24 08:10:21.921  7418  7418 D pmse_apk: pmse_render::vulkan::vulkan_init:   Adreno (TM) 640
09-24 08:10:21.921  7418  7418 D pmse_apk: pmse_render::vulkan::vulkan_init: vulkan device queue 3
09-24 08:10:21.921  7418  7418 D pmse_apk: pmse_render::vulkan::vulkan_init: vulkan queue index 0
09-24 08:10:21.923  7418  7418 I System.out: DEBUG: before nativeResize()
09-24 08:10:21.923  7418  7418 D pmse_apk: pmse_apk::jni: from rust: nativeResize(1080, 2244)
09-24 08:10:21.925  7418  7418 D pmse_apk: pmse_render::vulkan::swapchain:   image format: R8G8B8A8_UNORM
09-24 08:10:21.925  7418  7418 D pmse_apk: pmse_render::vulkan::swapchain:   min_image_count 2
09-24 08:10:21.938  7418  7418 I System.out: DEBUG: before nativeDraw()
09-24 08:10:21.938  7418  7418 D pmse_apk: pmse_apk::jni: from rust: nativeDraw()
09-24 08:10:21.938  7418  7418 D pmse_apk: pmse_render::vulkan::test::draw_t: vulkan_test T09-24 08:10:22.030  1832  1937 I ActivityTaskManager: Displayed io.github.fm_elpac.pmse_apk/.MainActivity: +351ms

大成功 ! 撒花 ~

4 总结与展望

kotlin 代码通过 SurfaceView 获得一块 “画布”, 然后用 JNI 调用 rust 代码. rust 代码初始化 vulkan 并渲染, 从而在 Android (手机) 画一个三角形. 这个结构实现了把一个 rust vulkan 渲染器 “嵌入” 一个普通的 Android apk 之中.

本文进一步验证了 vulkan 良好的 跨平台 能力. 只需要为每个平台编写少量的适配代码, 大部分代码都是可以直接复用的.

rust 对 Android 开发的支持, 已经达到了很好的状态. 前人已经把舞台搭建好了, 我们可以尽情发挥啦 ~

接下来会考虑 vulkan 在 Windows 平台的使用.


本文使用 CC-BY-SA 4.0 许可发布.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149855.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ITU标准引领车内通讯新纪元

在现代汽车科技更迭的今天&#xff0c;车内通讯与免提通话系统的性能与稳定性成为了消费者购车时不可忽视的重要因素。随着国际电信联盟&#xff08;ITU&#xff09;一系列标准的推出&#xff0c;车内通讯体验正迈向新的高度。本文将深入探讨ITU-T P.1100、P.1110、P.1120、P.1…

PTA L1-070 吃火锅

L1-070 吃火锅&#xff08;15分&#xff09; 以上图片来自微信朋友圈&#xff1a;这种天气你有什么破事打电话给我基本没用。但是如果你说“吃火锅”&#xff0c;那就厉害了&#xff0c;我们的故事就开始了。 本题要求你实现一个程序&#xff0c;自动检查你朋友给你发来的信息…

Django 对数据库的增删改查

新增 使用方法&#xff1a;类模型.objects.create 类模型 from django.db import models# Create your models here. class Car(models.Model):user models.CharField(max_length200)plate_number models.CharField(max_length20)def __str__(self):return f{self.user} -…

论文 | Reframing Instructional Prompts to GPTk’s Language

作者&#xff1a;Swaroop Mishra, Daniel Khashabi, Chitta Baral, Yejin Choi, Hannaneh Hajishirzi 论文摘要&#xff1a;语言模型 (LM) 更容易遵循哪些类型的指令提示&#xff1f; 我们通过进行广泛的实证分析来研究这个问题&#xff0c;这些分析阐明了成功指令提示的重要特…

vs2019 当前不会命中断点,还没有为该文档加载任何符号

问题&#xff1a; 解决问题&#xff1a; 1.检查C/C中的调试信息格式&#xff1b; 2. 检查连接器中的生成调试信息&#xff1b; 3.我的错误是修改过调试中的 “命令”这项导致的&#xff0c;下图已经改回默认&#xff1b;

责任链模式优化 文章发布的接口(长度验证,敏感词验证,图片验证等环节) 代码,示例

需求&#xff1a;后端需要提供一个文章发布的接口&#xff0c;接口中需要先对文章内容进行如下校验&#xff0c;校验通过后才能发布 1. 文章长度不能超过1万个字符 2. 不能有敏感词 3. 文章中图片需要合规 责任链相当于一个链条一样&#xff0c;链条上有很多节点&#xff0c;节…

基于STM32设计的室内育苗环境管理系统(物联网)

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成 1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发 1.5 模块的技术详情介绍【1】ESP8266-WIFI模块【2】MQ135传感器【4】DHT11传感器【5】B1750传感器 …

1. 值、类型与运算符

在计算机的世界里&#xff0c;只有数据。你可以读取数据、修改数据、创建新数据&#xff0c;但不能提及非数据的内容。所有这些数据都存储为长位序列&#xff0c;因此本质上是相似的。 位&#xff08;比特&#xff09;是任何类型的二值事物&#xff0c;通常被描述为0和1。在计算…

CORE Kestrel Web、InProcess、OutOfProcess、启动配置、读取配置文件

Kestrel 服务 ASP.NET Core是一个跨平台框架。 这意味着它支持在不同类型的操作系统&#xff08;例如Windows&#xff0c;Linux或Mac&#xff09;上开发和运行应用程序。 Kestrel是ASP.NET Core应用程序的跨平台Web服务器。 这意味着该服务器支持ASP.NET Core支持的所有平台和…

VIN码识别:提升汽车行业效率的智能解决方案

随着汽车行业的快速发展&#xff0c;汽车管理与服务的数字化需求日益增加。无论是汽车制造商、经销商、还是售后服务人员&#xff0c;都希望通过更便捷、高效的方式获取汽车的基本信息。在这样的背景下&#xff0c;VIN码识别接口成为了一种有效的智能解决方案。 什么是VIN码&am…

[笔记]某视觉三维定位系统参数表

表中的参数是彼此关联的&#xff0c;其实是就是视频解算的速度。里面的1秒直接对应1FPS300m秒直接对应3FPS0-20m的识别范围&#xff0c;与摄像头分辨率、视在焦距与摄像头基线有明确的对应关系。它的矩阵非正方。怀疑一组用于远距&#xff0c;一组用于近距&#xff0c;属于固定…

Redis系列补充:聊聊布隆过滤器(go语言实践篇)

1 介绍 布隆过滤器&#xff08;Bloom Filter&#xff09;是 Redis 4.0 版本之后提供的新功能&#xff0c;我们一般将它当做插件加载到 Redis Service服务器中&#xff0c;给 Redis 提供强大的滤重功能。 它是一种概率性数据结构&#xff0c;可用于判断一个元素是否存在于一个集…

Register Two Point Sets 注册两个点集

文章目录 Register Two Point Sets 注册两个点集Visualize Gradient Descent 可视化梯度下降Hyperparameter Search 超参数搜索JensenHavrdaCharvatTsallisPointSetToPointSetMetricv4类说明 原文url: https://examples.itk.org/src/registration/metricsv4/registertwopointse…

2024/9/24有关1x1卷积核

深度学习笔记&#xff08;六&#xff09;&#xff1a;1x1卷积核的作用归纳和实例分析_1x1卷积降维-CSDN博客 从这篇文章写的很好&#xff0c;主要讲的就是1x1卷积核有降维作用&#xff0c;还有就是线性映射作用&#xff08;一般步进长度设置为的为1&#xff0c;也相当于是全连…

R包:ggspatial空间画图

ggplot2语法的空间图形画图 Spatial data plus the power of the ggplot2 framework means easier mapping. 加载R包 # install.packages("ggspatial")library(ggplot2) library(ggspatial) load_longlake_data()Using layer_spatial() and annotation_spatial() g…

Sql Developer期显示格式设置

默认时间格式显示 设置时间格式&#xff1a;工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:MI:SS 设置完格式显示&#xff1a;

数学家发现一种新空间镶嵌形状

不知道你没有读过空间嵌合的相关理论。嵌合或者平铺在欧拉的时代就被研究透了&#xff0c;被认为是低矮的树木上已经没有果子。不过最近发现了一种新的镶嵌结构。 数学家这样描述了这种新的形状&#xff0c;这种形状在自然界中很常见ーー从鹦鹉螺标志性的螺旋壳的腔室&#xf…

百度C++一面-面经总结

1、你知道网络编程服务端建立连接的流程吗&#xff1f;把用到的api说出来&#xff1f; server&#xff1a; 1.socket() int sockfd socket(AF_INET, SOCK_STREAM, 0);2.bind() struct sockaddr_in serv_addr; serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr I…

C语言初识(一)

目录 前言 一、什么是C语言&#xff1f; 二、第一个C语言程序 &#xff08;1&#xff09;创建新项目 &#xff08;2&#xff09;编写代码 &#xff08;3&#xff09;main函数 三、数据类型 四、变量、常量 &#xff08;1&#xff09;变量的命名 &#xff08;2&#x…

003_动手实现MLP(详细版)

常见的激活的有&#xff1a;RELU,sigmoid,tanh代码 import torch import numpy as np import sys import d2lzh_pytorch as d2l import torchvision from torchvision import transforms # 1.数据预处理 mnist_train torchvision.datasets.FashionMNIST(root/Users/wPycharmP…