Name Date Size

..25-Oct-20244 KiB

.gitignoreH A D25-Oct-2024141

AppScope/H25-Oct-20244 KiB

build-profile.json5H A D25-Oct-20241.2 KiB

entry/H25-Oct-20244 KiB

hvigor/H25-Oct-20244 KiB

hvigorfile.tsH A D25-Oct-2024159

hvigorwH A D25-Oct-20241.4 KiB

hvigorw.batH A D25-Oct-20241.5 KiB

oh-package.json5H A D25-Oct-2024815

README.mdH A D25-Oct-2024425

README_zh.mdH A D25-Oct-202415.6 KiB

screenshots/device/H25-Oct-20244 KiB

README.md

1# Native Vulkan 3D Graphics
2## Introduction
3This sample mainly introduces how developers can use the Native window interface and XComponent to create 3D Graphics.
4
5## Usage
6The application page displays call to the Native window API and Vulkan API.
7
8## Constraints
9This sample can only be run on devices that support [VulkanAPI](https://gitee.com/openharmony/docs/tree/master/en/application-dev/reference/native-lib) drivers.

README_zh.md

1# XComponent组件对接Vulkan
2### 介绍
3XComponent组件作为绘制组件, 可用于满足开发者较为复杂的自定义绘制需求, 如相机预览流显示和游戏画面绘制。
4该组件分为`surface`类型和`component`类型, 可通过指定`type`字段来确定。 其中`surface`类型可支持开发者将相关数据传入XComponent单独拥有的surface来渲染画面。
5本篇示例基于"Native C++"模板, 演示了XComponent调用Vulkan API完成三角形绘制, 并将渲染结果显示在屏幕上的流程。
6
7示例主要使用[@ohos.app.ability.UIAbility](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/Readme-CN.md), 
8[@ohos.hilog](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/dfx), 
9[@ohos.window](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/windowmanager/window-overview.md), 
10[NativeWindow](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/graphics/native-window-guidelines.md), 
11[Vulkan](https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/reference/native-lib)接口。
12
13> **说明**
14> 本示例XComponent接口使用方法已停止演进,推荐使用方法请参考[ArkTSXComponent示例](../ArkTSXComponent/README_zh.md)。
15### 效果预览
16如下图所示, 打开应用, 屏幕中心会绘制一个旋转中的三角形, 可以点击'stop/start'按钮控制三角形的旋转状态。
17![image](screenshots/device/sample.png)
18### 工程目录
19
20```
21entry/src/main/
22|---cpp
23|   |---common
24|   |   |---logger_common.h                          // Hilog日志宏定义
25|   |---render
26|   |   |---vulkan
27|   |   |   |---shader
28|   |   |   |   |   |---triangle.frag                // fragment shader
29|   |   |   |   |   |---triangle.vert                // vertex shader
30|   |   |   |---vulkan_example.h                     // 本示例中用于实现三角形绘制的VulkanExample类
31|   |   |   |---vulkan_example.cpp
32|   |   |   |---vulkan_utils.h                       // 本示例中用于加载Vulkan动态库以及Vulkan函数
33|   |   |   |---vulkan_utils.cpp
34|   |   |---plugin_manager.h                         // 对接XComponent
35|   |   |---plugin_manager.cpp
36|   |   |---plugin_render.h                          // 对接后端VulkanExample, 使能Vulkan能力
37|   |   |---plugin_render.cpp
38|   |---CMakeLists.txt
39|   |---plugin.cpp                                   // 注册NAPI
40|---ets
41|   |---entryability
42|   |   |---EntryAbility.ts                          // 定义组件的入口点以及日志类封装
43|   |---pages
44|   |   |---Index.ets                                // 应用界面布局描述以及shader二进制加载
45```
46
47### 具体实现
48#### XComponent
49XComponent组件可通过NDK接口为开发者在C++层提供NativeWindow用于创建Vulkan环境.
50##### NAPI注册
51首先填充`napi_module`结构体, 然后调用`napi_module_register`函数注册.
52```
53// entry/src/main/cpp/plugin.cpp
54static napi_module SampleModule = {
55    .nm_version = 1,
56    .nm_flags = 0,
57    .nm_filename = nullptr,
58    .nm_register_func = Init, // 指定加载C++层编译的动态so的NAPI注册函数名
59    .nm_modname = "nativerender", // 指定被NAPI加载的so的名称, 应与cpp目录下CMakeLists.txt指定的编译so名称一致
60    .nm_priv = ((void *)0),
61    .reserved = {0},
62};
63
64extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
65    napi_module_register(&SampleModule);
66}
67```
68注意`napi_module`中的成员变量`.nm_register_func`指定了加载so时的注册函数名为`Init`, 我们通过修改该函数来控制注册NAPI时要执行的操作.
69在Init函数中我们封装了一个单例类PluginManager, 以应对多个NativeXComponent实例的情况.
70```
71// entry/src/main/cpp/plugin.cpp
72static napi_value Init(napi_env env, napi_value exports)
73{
74    if (!PluginManager::GetInstance()->Init(env, exports)) {
75        LOGE("Failed to init NAPI!");
76    }
77    return exports;
78}
79```
80##### PluginManager
81PluginManager::Init函数中主要做了两件事
82###### 获取NativeXComponent指针
83`NativeXComponent`为`XComponent`提供了在`native`层的实例, 可作为ts/js层与`native`层`XComponent`绑定的桥梁.
84```
85// entry/src/main/cpp/render/plugin_manager.cpp
86bool PluginManager::Init(napi_env env, napi_value exports)
87{
88    napi_value exportInstance = nullptr;
89    OH_NativeXComponent *nativeXComponent = nullptr; // NativeXComponent指针
90    // 首先调用napi_get_name_property, 传入OH_NATIVE_XCOMPONENT_OBJ, 解析得到exportInstance.
91    napi_status status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
92    // 然后调用napi_unwrap, 从exportInstance中解析得到nativeXComponent实例指针
93    status = napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent));
94    ...
95}
96```
97
98###### 创建和初始化PluginRender
99`PluginManager`可以管理多个`PluginRender`实例(示例中只使用一个), 在`PluginManager::Init`中进行`PluginRender`的创建和初始化.
100实际对接`Vulkan`渲染后端是通过`PluginRender`类完成的, 每个`XComponent`实例对应一个`PluginRender`实例, 通过`XComponentId`进行区分.
101```
102// entry/src/main/cpp/render/plugin_manager.cpp
103bool PluginManager::Init(napi_env env, napi_value exports)
104{
105    // 省略获取nativeXComponent的部分
106
107    // 获取XComponentId
108    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};
109    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
110    // get nativeXComponent Id 
111    int32_t ret = OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize);
112    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
113        LOGE("PluginManager::Export OH_NativeXComponent_GetXComponentId failed, ret:%{public}d", ret);
114        return false;
115    }
116
117    std::string id(idStr);
118    // 获取PluginManger单例指针
119    auto context = PluginManager::GetInstance();
120    if (context != nullptr) {
121        context->SetNativeXComponent(id, nativeXComponent);
122        // 传入XComponentId初始化PluginRender
123        auto render = context->GetRender(id);
124        if (render == nullptr) {
125            LOGE("Failed to get render context!");
126            return false;
127        }
128        render->Export(env, exports); // 注册开放给js/ts层调用的native函数
129        render->SetNativeXComponent(nativeXComponent); // 设置navetiveXComponent指针和回调函数
130        return true;
131    }
132    LOGE("Failed to get PluginManager instance! XComponentId:%{public}s", idStr);
133    return false;
134```
135
136##### PluginRender
137###### 注册XComponent事件回调
138在`PluginManager::Init`中解析得到`NativeXComponent`指针后, 通过`PluginRender::SetNativeXComponent`将指针传递给`PluginRender`并调用`OH_NativeXComponent_RegisterCallback`注册事件回调.
139```
140// entry/src/main/cpp/render/plugin_render.cpp
141OH_NativeXComponent_Callback PluginRender::callback_;
142
143OH_NativeXComponent_Callback *PluginRender::GetNXComponentCallback() {
144    return &PluginRender::callback_;
145}
146
147PluginRender::PluginRender(std::string &id) : id_(id), component_(nullptr)
148{
149    auto renderCallback = PluginRender::GetNXComponentCallback();
150    renderCallback->OnSurfaceCreated = OnSurfaceCreatedCB;     // surface创建成功后触发,开发者可以从中获取native window的句柄
151    renderCallback->OnSurfaceChanged = OnSurfaceChangedCB;     // surface发生变化后触发,开发者可以从中获取native window的句柄以及XComponent的变更信息
152    renderCallback->OnSurfaceDestroyed = OnSurfaceDestroyedCB; // surface销毁时触发,开发者可以在此释放资源
153    renderCallback->DispatchTouchEvent = DispatchTouchEventCB; // XComponent的touch事件回调接口,开发者可以从中获得此次touch事件的信息
154}
155
156void PluginRender::SetNativeXComponent(OH_NativeXComponent *component)
157{
158    component_ = component;
159    OH_NativeXComponent_RegisterCallback(component_, &PluginRender::callback_); // 注册事件回调
160}
161```
162###### 定义暴露给前端的方法
163`XComponent`支持将`native`方法暴露给前端调用, 一般将该函数命名为`Export`, 它会通过js引擎绑定到js层的一个js对象
164开发者首先需要填充结构体`napi_property_descriptor`, 再调用`napi_define_properties`完成注册.
165下面的示例代码中将native方法`stopOrStart`暴露给前端, 后续会关键到触控事件, 使得用户能通过按钮控制本示例中三角形的旋转.
166```
167// entry/src/main/cpp/render/plugin_render.cpp
168napi_value PluginRender::Export(napi_env env, napi_value exports)
169{
170    napi_property_descriptor desc[] = {
171        { "stopOrStart", nullptr, PluginRender::NapiStopMovingOrRestart, nullptr, nullptr, nullptr,
172            napi_default, nullptr}
173    };
174    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
175    return exports;
176}
177```
178###### 调用Vulkan后端
179在`Surface`创建时触发的回调函数里创建`Vulkan`环境和渲染管线, 并将绘制函数绑定到主线程.
180```
181// entry/src/main/cpp/render/plugin_render.cpp
182void PluginRender::OnSurfaceCreated(OH_NativeXComponent *component, void *window)
183{
184    int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width_, &height_);
185    if (vulkanExample_ == nullptr) {
186        vulkanExample_ = std::make_unique<vkExample::VulkanExample>();
187        vulkanExample_->SetupWindow(static_cast<OHNativeWindow *>(window));
188        if (!vulkanExample_->InitVulkan()) {
189            LOGE("PluginRender::OnSurfaceCreated vulkanExample initVulkan failed!");
190            return;
191        }
192        vulkanExample_->SetUp();
193        renderThread_ = std::thread(std::bind(&PluginRender::RenderThread, this));
194    }
195}
196
197void PluginRender::RenderThread()
198{
199    while (vulkanExample_ != nullptr && vulkanExample_->IsInited()) {
200        std::unique_lock<std::mutex> locker(mutex_);
201        if (isTriangleRotational_) {
202            vulkanExample_->RenderLoop();
203        } else {
204            con_.wait(locker);
205        }
206    }
207}
208```
209
210#### Vulkan后端
211本示例中用于绘制三角形的`Vulkan`后端被封装成了类`VulkanExample`, 它对外部暴露6个接口:
212```
213bool InitVulkan();                             // 用于初始化Vulkan环境, 包括加载vulkan动态库, 创建Instance, 选择PhysicalDevice以及创建LogicalDevice
214void SetupWindow(NativeWindow* nativeWindow);  // 将NativeXComponent的NativeWindow指针传入, 用于surface创建
215void SetUp();                                  // 创建Swapchain, ImageView及渲染相关组件
216void RenderLoop();                             // 在渲染线程循环调用, 用于绘制三角形
217bool IsInited() const;                         // 判断Vulkan环境是否初始化成功
218void SetRecreateSwapChain();                   // 设置下次渲染前重建swapchain
219```
220因为本示例主要用于展示`XComponent`组件调用`Vulkan`API的流程, 因此对Vulkan绘制三角形的一般流程不做讲解(相关知识可参考[Vulkan官方指导](https://vulkan-tutorial.com/)).
221仅讲解与`XComponent`以及`OpenHarmony`相关的部分, 更多`OpenHarmony VulkanAPI`使用指导可参考[鸿蒙Vulkan](https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/reference/native-lib).
222##### libvulkan.so动态库加载
223OpenHarmony操作系统中Vulkan动态库的名称是`libvulkan.so`, 可通过`dlopen`函数加载.
224
225示例代码:
226```
227#include <dlfcn.h>
228
229const char* path_ = "libvulkan.so";
230void libVulkan = dlopen(path_, RTLD_NOW | RTLD_LOCAL);
231```
232
233##### Vulkan函数加载
234Vulkan函数分为`Instance`域函数, `PhysicalDevice`域函数, `Device`域函数.
235
236`Instance`域函数中的`全局函数`可通过`dlsym`函数获取其函数指针.
237
238示例代码:
239```
240// 全局函数加载
241#include <dlfcn.h>
242// 省略libvulkan.so的加载
243PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = 
244    reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(dlsym(libVulkan, "vkEnumerateInstanceExtensionProperties"));
245PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties =
246    reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(dlsym(libVulkan, "vkEnumerateInstanceLayerProperties"));
247PFN_vkCreateInstance vkCreateInstance =
248    reinterpret_cast<PFN_vkCreateInstance>(dlsym(libVulkan, "vkCreateInstance"));
249PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
250    reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(libVulkan, "vkGetInstanceProcAddr"));
251PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr =
252    reinterpret_cast<PFN_vkGetDeviceProcAddr>(dlsym(libVulkan, "vkGetDeviceProcAddr"));
253```
254
255在获取`vkGetInstanceProcAddr`函数后, 可通过它加载`Instance`域函数和`PhysicalDevice`域函数; 在获取`vkGetDeviceProcAddr`函数后, 可通过它加载`Device`域函数.
256
257示例代码:
258```
259// Instance域函数加载
260PFN_vkCreateDevice vkCreateDevice =
261    reinterpret_cast<PFN_vkCreateDevice>(vkGetInstanceProcAddr(instance, "vkCreateDevice"));
262
263// Device域函数加载
264PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR =
265    reinterpret_cast<PFN_vkCreateSwapchainKHR>(vkGetDeviceProcAddr(device, "vkCreateSwapchainKHR"));
266```
267##### Instance创建
268创建`Instance`时, 为保证后续能成功创建`OpenHarmony`平台下的`surface`, 需要开启extension `VK_OHOS_SURFACE_EXTENSION_NAME`和`VK_KHR_SURFACE_EXTENSION_NAME`.
269
270示例代码:
271```
272bool VulkanExample::CreateInstance() {
273    VkInstanceCreateInfo createInfo{};
274    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
275    ...
276    // 省略其他成员变量定义过程
277    ...
278    std::vector<const char *> extensions = { VK_KHR_SURFACE_EXTENSION_NAME, VK_OHOS_SURFACE_EXTENSION_NAME };
279    createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
280    createInfo.ppEnabledExtensionNames = extensions.data();
281    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
282        LOGE("Failed to create instance!");
283        return false;
284    }
285    return true;
286}
287```
288##### Surface创建
289OHOS上的`surface`创建需要填写结构体`VkSurfaceCreateInfoOHOS`, XCompoenent通过`SetupWindow`函数传入`window`指针用于`surface`的创建.
290
291示例代码:
292```
293// window为VulkanExample的成员变量, 其类型为NativeWindow*
294void VulkanExample::SetupWindow(NativeWindow* nativeWindow)
295{
296    window = nativeWindow;
297}
298
299bool VulkanExample::CreateSurface() {
300    VkSurfaceCreateInfoOHOS surfaceCreateInfo{};
301    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_SURFACE_CREATE_INFO_OHOS;
302    if (window == nullptr) {
303        LOGE("Nativewindow is nullptr. Failed to create surface!");
304        return false;
305    }
306    surfaceCreateInfo.window = window;
307    if (vkCreateSurfaceOHOS(instance, &surfaceCreateInfo, nullptr, &surface) != VK_SUCCESS) {
308        LOGE("Failed to create OHOS surface!");
309        return false;
310    }
311    return true;
312}
313
314```
315### 相关权限
316本示例不涉及特殊系统权限。
317### 依赖
318本示例不依赖其他sample。
319### 约束与限制
3201. 本示例要求设备底层驱动已实现[Vulkan API接口](https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/reference/native-lib), rk开发板目前不支持(需厂商驱动实现)。
3212. 本示例基于OpenHarmony API 11 SDK(4.1.7.5)。
322   如果想要在HarmonyOS工程上运行, 需保证HarmonyOS SDK版本为API10及以上, 运行方法如下:
323
324   1. 新建一个HarmonyOS Native C++工程;
325   2. 将本工程AppScope/resources/rawfile目录拷贝至新工程AppScope/resources目录下;
326   3. 删除新工程entry/src目录, 将本工程entry目录下所有文件拷贝至新工程的entry目录下。
3273. 本示例需要使用DevEco Studio版本号(4.0 Release)及以上版本才可编译运行。
328### 下载
329如需单独下载本工程,执行如下命令:
330```
331git init
332git config core.sparsecheckout true
333echo code/BasicFeature/Native/NdkVulkan/ > .git/info/sparse-checkout
334git remote add origin https://gitee.com/openharmony/applications_app_samples.git
335git pull origin master
336```