我们可以在 Android 平台上将 Vulkan 与 Java Activity 一起使用吗
Can we use Vulkan with Java Activity on Android platform
目前,似乎所有 Vulkan 教程和示例都在 Android 平台上使用 NativeActivity。我想知道我们是否可以在 Android 上将 Vulkan 与 Java Activity 一起使用?
是的,您可以将 Vulkan 与您自己的 Activity 子类一起使用。因为 Android 没有 Vulkan 的 Java 语言绑定,你需要使用 JNI 或第三方 Java Vulkan 库(它只是为你做 JNI ).
您的视图层次结构需要包含一个 SurfaceView, and when you get the Surfaceholder.Callback#surfaceChanged callback you can get the Surface. If you're doing the JNI yourself, you can call ANativeWindow_fromSurface 才能从 Surface 获取 ANativeWindow,并使用它来创建您的 VkSurfaceKHR/VkSwapchainKHR。
要注意的一件事是避免在调用 VkAcquireNextImageKHR 时阻塞主 UI 线程。要么安排它只在它不会长时间阻塞时调用它,要么将你的帧循环放在一个单独的线程上。
假设您有一个封装了 Vulkan 绘图逻辑的 C++ class:
// File: AndroidGraphicsApplication.hpp
#include <android/asset_manager.h>
#include <android/native_window.h>
#include "GraphicsApplication.h" // Base class shared with iOS/macOS/...
class AndroidGraphicsApplication : public GraphicsApplication {
public:
AndroidGraphicsApplication(AAssetManager* assetManager, ANativeWindow* window): GraphicsApplication() {
mAssetManager = assetManager;
mWindow = window;
// ... Vulkan initialisation code.
}
~AndroidGraphicsApplication() {
// ... Vulkan cleanup code.
}
void createSurface() {
VkAndroidSurfaceCreateInfoKHR surface_info;
surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
surface_info.pNext = NULL;
surface_info.flags = 0;
surface_info.window = mWindow;
if(vkCreateAndroidSurfaceKHR(instance, &surface_info, NULL, &surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface!");
}
}
// Used to setup shaders.
std::vector<char> readFile(const std::string& filename) {
AAsset* file = AAssetManager_open(mAssetManager, filename.c_str(), AASSET_MODE_BUFFER);
size_t size = AAsset_getLength(file);
std::vector<char> data(size);
AAsset_read(file, data.data(), size);
AAsset_close(file);
return data;
}
void setSize(uint32_t w, uint32_t h) {
width = w;
height = h;
}
private:
AAssetManager* mAssetManager;
ANativeWindow* mWindow;
uint32_t width;
uint32_t height;
};
并且您有如下所示的 JNI 桥接器:
// File: VulkanAppBridge.cpp
#include <android/log.h>
#include <android/native_window_jni.h>
#include <android/asset_manager_jni.h>
#include "AndroidGraphicsApplication.hpp"
AndroidGraphicsApplication *mApplicationInstance = NULL;
extern "C" {
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeCreate(JNIEnv *env, jobject vulkanAppBridge,
jobject surface, jobject pAssetManager) {
if (mApplicationInstance) {
delete mApplicationInstance;
mApplicationInstance = NULL;
}
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "create");
auto window = ANativeWindow_fromSurface(env, surface);
auto assetManager = AAssetManager_fromJava(env, pAssetManager);
mApplicationInstance = new AndroidGraphicsApplication(assetManager, window);
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeDestroy(JNIEnv *env, jobject vulkanAppBridge) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "destroy");
if (mApplicationInstance) {
delete mApplicationInstance;
mApplicationInstance = NULL;
}
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeResize(JNIEnv *env, jobject vulkanAppBridge, jint width, jint height) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "resize: %dx%d", width, height);
if (mApplicationInstance) {
mApplicationInstance->setSize(width, height);
mApplicationInstance->isResizeNeeded = true;
}
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeDraw(JNIEnv *env, jobject vulkanAppBridge) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "draw");
if (mApplicationInstance) {
mApplicationInstance->drawFrame();
}
}
}
并且你有相应的 Java/Kotlin 部分 JNI 桥:
// File: VulkanAppBridge.kt
class VulkanAppBridge {
init {
System.loadLibrary("myApplication")
}
private external fun nativeCreate(surface: Surface, assetManager: AssetManager)
private external fun nativeDestroy()
private external fun nativeResize(width: Int, height: Int)
private external fun nativeDraw()
fun create(surface: Surface, assetManager: AssetManager) {
nativeCreate(surface, assetManager)
}
fun destroy() {
nativeDestroy()
}
fun resize(width: Int, height: Int) {
nativeResize(width, height)
}
fun draw() {
nativeDraw()
}
}
并且您有 SurfaceView
的自定义子 class:
// File: VulkanSurfaceView.kt
class VulkanSurfaceView: SurfaceView, SurfaceHolder.Callback2 {
private var vulkanApp = VulkanAppBridge()
constructor(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 = 1F
holder.addCallback(this)
}
// ...
// Implementation code similar to one in GLSurfaceView is skipped.
// See: https://android.googlesource.com/platform/frameworks/base/+/master/opengl/java/android/opengl/GLSurfaceView.java
// ...
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
vulkanApp.resize(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
vulkanApp.destroy()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
holder?.let { h ->
vulkanApp.create(h.surface, resources.assets)
}
}
override fun surfaceRedrawNeeded(holder: SurfaceHolder?) {
vulkanApp.draw()
}
}
然后您可以使用具有自定义尺寸的自定义 VulkanSurfaceView
内部布局以及其他视图:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id= "@+id/linearlayout1" >
<Button
android:id="@+id/mcButtonTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Button" />
<com.mc.demo.vulkan.MyGLSurfaceView
android:id="@+id/mcSurfaceView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.23" />
<!-- Custom SurfaceView -->
<com.mc.demo.vulkan.VulkanSurfaceView
android:id="@+id/mcVulkanSurfaceView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.23" />
<Button
android:id="@+id/mcButtonBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Button" />
</LinearLayout>
结果:
这是一个 link 到 "Vulkan Case Study" 的 Android 用法示例:https://www.khronos.org/assets/uploads/developers/library/2016-vulkan-devu-seoul/2-Vulkan-Case-Study.pdf
目前,似乎所有 Vulkan 教程和示例都在 Android 平台上使用 NativeActivity。我想知道我们是否可以在 Android 上将 Vulkan 与 Java Activity 一起使用?
是的,您可以将 Vulkan 与您自己的 Activity 子类一起使用。因为 Android 没有 Vulkan 的 Java 语言绑定,你需要使用 JNI 或第三方 Java Vulkan 库(它只是为你做 JNI ).
您的视图层次结构需要包含一个 SurfaceView, and when you get the Surfaceholder.Callback#surfaceChanged callback you can get the Surface. If you're doing the JNI yourself, you can call ANativeWindow_fromSurface 才能从 Surface 获取 ANativeWindow,并使用它来创建您的 VkSurfaceKHR/VkSwapchainKHR。
要注意的一件事是避免在调用 VkAcquireNextImageKHR 时阻塞主 UI 线程。要么安排它只在它不会长时间阻塞时调用它,要么将你的帧循环放在一个单独的线程上。
假设您有一个封装了 Vulkan 绘图逻辑的 C++ class:
// File: AndroidGraphicsApplication.hpp
#include <android/asset_manager.h>
#include <android/native_window.h>
#include "GraphicsApplication.h" // Base class shared with iOS/macOS/...
class AndroidGraphicsApplication : public GraphicsApplication {
public:
AndroidGraphicsApplication(AAssetManager* assetManager, ANativeWindow* window): GraphicsApplication() {
mAssetManager = assetManager;
mWindow = window;
// ... Vulkan initialisation code.
}
~AndroidGraphicsApplication() {
// ... Vulkan cleanup code.
}
void createSurface() {
VkAndroidSurfaceCreateInfoKHR surface_info;
surface_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
surface_info.pNext = NULL;
surface_info.flags = 0;
surface_info.window = mWindow;
if(vkCreateAndroidSurfaceKHR(instance, &surface_info, NULL, &surface) != VK_SUCCESS) {
throw std::runtime_error("failed to create window surface!");
}
}
// Used to setup shaders.
std::vector<char> readFile(const std::string& filename) {
AAsset* file = AAssetManager_open(mAssetManager, filename.c_str(), AASSET_MODE_BUFFER);
size_t size = AAsset_getLength(file);
std::vector<char> data(size);
AAsset_read(file, data.data(), size);
AAsset_close(file);
return data;
}
void setSize(uint32_t w, uint32_t h) {
width = w;
height = h;
}
private:
AAssetManager* mAssetManager;
ANativeWindow* mWindow;
uint32_t width;
uint32_t height;
};
并且您有如下所示的 JNI 桥接器:
// File: VulkanAppBridge.cpp
#include <android/log.h>
#include <android/native_window_jni.h>
#include <android/asset_manager_jni.h>
#include "AndroidGraphicsApplication.hpp"
AndroidGraphicsApplication *mApplicationInstance = NULL;
extern "C" {
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeCreate(JNIEnv *env, jobject vulkanAppBridge,
jobject surface, jobject pAssetManager) {
if (mApplicationInstance) {
delete mApplicationInstance;
mApplicationInstance = NULL;
}
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "create");
auto window = ANativeWindow_fromSurface(env, surface);
auto assetManager = AAssetManager_fromJava(env, pAssetManager);
mApplicationInstance = new AndroidGraphicsApplication(assetManager, window);
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeDestroy(JNIEnv *env, jobject vulkanAppBridge) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "destroy");
if (mApplicationInstance) {
delete mApplicationInstance;
mApplicationInstance = NULL;
}
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeResize(JNIEnv *env, jobject vulkanAppBridge, jint width, jint height) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "resize: %dx%d", width, height);
if (mApplicationInstance) {
mApplicationInstance->setSize(width, height);
mApplicationInstance->isResizeNeeded = true;
}
}
JNIEXPORT void JNICALL
Java_com_mc_demo_vulkan_VulkanAppBridge_nativeDraw(JNIEnv *env, jobject vulkanAppBridge) {
__android_log_print(ANDROID_LOG_DEBUG, "mc-native-VulkanAppBridge", "draw");
if (mApplicationInstance) {
mApplicationInstance->drawFrame();
}
}
}
并且你有相应的 Java/Kotlin 部分 JNI 桥:
// File: VulkanAppBridge.kt
class VulkanAppBridge {
init {
System.loadLibrary("myApplication")
}
private external fun nativeCreate(surface: Surface, assetManager: AssetManager)
private external fun nativeDestroy()
private external fun nativeResize(width: Int, height: Int)
private external fun nativeDraw()
fun create(surface: Surface, assetManager: AssetManager) {
nativeCreate(surface, assetManager)
}
fun destroy() {
nativeDestroy()
}
fun resize(width: Int, height: Int) {
nativeResize(width, height)
}
fun draw() {
nativeDraw()
}
}
并且您有 SurfaceView
的自定义子 class:
// File: VulkanSurfaceView.kt
class VulkanSurfaceView: SurfaceView, SurfaceHolder.Callback2 {
private var vulkanApp = VulkanAppBridge()
constructor(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 = 1F
holder.addCallback(this)
}
// ...
// Implementation code similar to one in GLSurfaceView is skipped.
// See: https://android.googlesource.com/platform/frameworks/base/+/master/opengl/java/android/opengl/GLSurfaceView.java
// ...
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
vulkanApp.resize(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
vulkanApp.destroy()
}
override fun surfaceCreated(holder: SurfaceHolder?) {
holder?.let { h ->
vulkanApp.create(h.surface, resources.assets)
}
}
override fun surfaceRedrawNeeded(holder: SurfaceHolder?) {
vulkanApp.draw()
}
}
然后您可以使用具有自定义尺寸的自定义 VulkanSurfaceView
内部布局以及其他视图:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id= "@+id/linearlayout1" >
<Button
android:id="@+id/mcButtonTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Button" />
<com.mc.demo.vulkan.MyGLSurfaceView
android:id="@+id/mcSurfaceView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.23" />
<!-- Custom SurfaceView -->
<com.mc.demo.vulkan.VulkanSurfaceView
android:id="@+id/mcVulkanSurfaceView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.23" />
<Button
android:id="@+id/mcButtonBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Button" />
</LinearLayout>
结果:
这是一个 link 到 "Vulkan Case Study" 的 Android 用法示例:https://www.khronos.org/assets/uploads/developers/library/2016-vulkan-devu-seoul/2-Vulkan-Case-Study.pdf