冲破 ArkTS 性能极限,XComponent 融合 C++ 与 OpenGL ES 渲染
上周在做一款实时滤镜相机应用时,遇到了严重的性能瓶颈。在 ArkTS 层使用 Image 结合 Canvas 进行像素级图像处理,处理一张 1080P 的图片耗时超过了 2 秒,界面直接卡死。要解决这种计算密集型与渲染密集型叠加的需求,唯一的出路就是下沉到 Native 层,用 C++ 结合 OpenGL ES 来接管渲染管线。
构建跨语言的渲染桥梁
要在 ArkTS 中嵌入原生的 OpenGL 渲染,核心组件是 XComponent。它会在底层创建一个 Surface,并允许我们将这个 Surface 的 ID 传递给 C++ 层。
typescript
@Component
struct GlFilterView {
private xComponentController: XComponentController = new XComponentController();
build() {
XComponent({
id: 'gl_surface',
type: XComponentType.SURFACE,
// 指定加载的 native so 库名称
libraryname: 'native_renderer'
})
.onLoad(() => {
// 将 SurfaceID 传递给原生层
const surfaceId = this.xComponentController.getXComponentSurfaceId();
nativeRender.initRenderer(surfaceId);
})
.onDestroy(() => {
nativeRender.releaseRenderer();
})
.width('100%')
.height('100%')
}
}
这里有一个极大的警示:onLoad 回调是在 UI 树绑定完成后才触发的。如果你在 aboutToAppear 里就试图去拿 Surface ID 并传递给 C++,原生层拿到的一定是空指针,导致应用直接闪退。
C++ 层的 EGL 环境初始化
在 C++ 侧,我们需要通过 NAPI 接收 ArkTS 传来的 Surface ID,并利用 EGL 库将其与 OpenGL 的上下文绑定。
cpp
#include <EGL/egl.h>
#include <GLES3/gl3.h>
void InitRenderer(std::string surfaceId) {
// 获取原生窗口
OHNativeWindow* window = nullptr;
OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId.c_str(), &window);
// 初始化 EGLDisplay
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, nullptr, nullptr);
// 配置 EGLConfig(选择 RGB888 等格式)
EGLConfig config;
EGLint numConfigs;
EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE };
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
// 创建 EGLSurface,绑定原生窗口
EGLSurface surface = eglCreateWindowSurface(display, config, window, nullptr);
// 创建并绑定 EGLContext(激活 OpenGL 渲染上下文)
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, nullptr);
eglMakeCurrent(display, surface, surface, context);
}
这段代码是原生渲染的命脉。如果不调用 eglMakeCurrent,后续所有的 OpenGL 指令(如 glClear、glDrawArrays)都会失效,且不会报错。
着色器编写与双缓冲提交
滤镜的核心在于片元着色器(Fragment Shader)。我们需要写一段 GLSL 代码来实现黑白或者复古效果,然后编译并链接到程序中。
每一次渲染循环(Render Loop),我们需要在 C++ 侧的独立渲染线程里执行绘制,并调用 eglSwapBuffers 提交画面。注意:绝不能把这个渲染循环放在 ArkTS 的主线程里,否则不仅毫无性能可言,还会拖垮整个应用的响应速度。
生命周期与内存回收
原生渲染最容易发生内存泄漏。当用户在应用中穿梭,销毁包含 XComponent 的页面时,onDestroy 被触发。此时在 C++ 侧,必须按严格顺序销毁资源:
-
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); -
eglDestroySurface(display, surface); -
eglDestroyContext(display, context); -
eglTerminate(display);
遗漏其中任何一步,都会导致底层的 GPU 内存无法释放,几次页面进出后,应用就会因为显存耗尽而闪退。
准备两张图片,一张作为背景,一张作为蒙版。在 C++ 侧的片元着色器里,通过纹理采样(Texture Sampling),根据蒙版像素的明暗度,动态混合两张图片。编写一个 NAPI 接口暴露给你的 ArkTS 页面,尝试用滑动条动态调节混合比例,感受原生渲染的丝滑。
更多推荐
所有评论(0)