鸿蒙VSync及多线程并行渲染实践
本文介绍了基于OpenGLES的多线程并行渲染技术实践,通过主线程调度与子线程离屏渲染相结合的方式提升图形性能。关键技术包括:使用EGL Pbuffer Surface和FBO实现子线程离屏渲染;通过共享上下文实现资源复用;采用条件变量和原子变量实现线程间高效同步;支持动态调整渲染尺寸。该方案有效解决了单线程渲染瓶颈,将帧率提升至目标水平,同时确保线程安全和资源管理。核心优势在于零CPU占用等待、
鸿蒙VSync及多线程并行渲染实践
本案例基于 OpenGLES 图形接口,演示了如何利用多线程技术并行处理渲染任务。方案通过主线程统一调度渲染时机,将绘制任务拆分至多个子线程并发执行,最终合成并上屏显示。演示效果为一个旋转的三角形,通过动态调整线程数量,可有效解决单线程绘制瓶颈,将因绘制耗时导致的低帧率提升至目标水平。
查看 源码
核心知识点
通过本案例的实践,您将深入掌握以下高性能图形渲染的关键技术:
- VSync (垂直同步) 机制:深入理解屏幕刷新信号的硬件原理,掌握如何利用
OH_NativeVSync构建精确的渲染循环,实现渲染帧率与屏幕刷新率的完美同步,消除画面撕裂与卡顿。 - OpenGLES 离屏渲染技术:精通 EGL Pbuffer Surface 与 FBO(帧缓冲对象)的结合使用,掌握在非屏幕缓冲区进行独立绘制的方法,学习如何通过纹理附件将渲染结果输出,为多线程并行处理奠定基础。
- 多线程并行渲染架构:学习多线程环境下的 EGL 上下文共享与资源管理,掌握主线程调度与子线程执行(生产者-消费者)的设计模式,熟练运用 C++ 原子变量、条件变量及互斥锁实现高效的任务分发与线程同步。
- NAPI 桥接与跨线程通信:掌握 Node-API (NAPI) 的开发规范,理解 Native C++ 与 ArkTS 之间的数据传递机制。重点学习使用
napi_threadsafe_function实现从后台渲染线程到 UI 主线程的安全数据回传,确保应用的稳定性。 - GPU 高效合成技术:学习使用
glBlitFramebuffer替代传统绘制流程,实现 GPU 内部的像素数据直接拷贝,大幅降低 CPU 占用与内存带宽消耗,提升上屏效率。
一、子线程离屏渲染
离屏渲染(Offscreen Rendering)是一种在非屏幕缓冲区进行图形绘制的技术。它允许应用程序在后台完成复杂的渲染操作,
然后将渲染结果作为纹理传递到主屏幕进行最终展示。这种技术对于多线程并行渲染至关重要,
因为它可以确保子线程独立完成绘制任务,而不影响主线程的渲染流程。
1. EGL 上下文管理
离屏渲染需要独立的 EGL 上下文,与主上下文共享资源:
// 创建离屏渲染绘制表面(使用 Pbuffer)
EGLint surfaceAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
surface = eglCreatePbufferSurface(display, config, surfaceAttribs);
// 创建离屏渲染上下文,与主上下文共享资源
context = eglCreateContext(display, config, info->main_context, info->egl_option.egl_context_attrib_list);
- Pbuffer Surface:用于创建离屏渲染表面,尺寸设为 1x1,因为实际渲染使用 FBO
- 共享上下文:通过传入
main_context,实现资源(纹理、着色器等)的共享
2. 帧缓冲对象(FBO)
FBO 是离屏渲染的核心,用于将渲染结果输出到纹理:
// 创建帧缓冲和纹理
glGenFramebuffers(1, &frame_buffer);
glGenTextures(1, &texture_id);
// 绑定纹理并分配内存
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// 将纹理附加为 FBO 的颜色附件
glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
3. 渲染流程
离屏渲染的标准流程包括:
- 绑定渲染环境
bool OffscreenRenderContext::Bind() { glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); glBindTexture(GL_TEXTURE_2D, texture_id); glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); return true; } - 执行绘制
void OffscreenRenderContext::Draw() { if (render_draw_callback != nullptr) { render_draw_callback(data); } } - 解绑并同步
bool OffscreenRenderContext::Unbind() { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); return true; } // 确保渲染完成 glFinish();
线程管理
RenderThread::RenderThread(OffscreenRenderContextInfo* info) {
stop.store(false);
render_done.store(true);
ready_to_render = false;
context_init = false;
this->render_context = new OffscreenRenderContext(info);
// 启动渲染线程
render_thread = std::thread(&RenderThread::RunRenderThread, this);
}
渲染循环
渲染线程通过条件变量实现高效的等待-唤醒机制:
void RenderThread::RunRenderThread(RenderThread *render_vsync) {
while (true) {
std::unique_lock<std::mutex> lock(render_vsync->mtx);
// 等待渲染信号或停止信号
render_vsync->cv.wait(lock, [render_vsync] {
return render_vsync->ready_to_render || render_vsync->stop.load();
});
if (render_vsync->stop.load()) {
break;
}
render_vsync->ready_to_render = false;
lock.unlock(); // 关键:解锁后执行渲染,避免阻塞主线程
// 执行离屏渲染
render_vsync->Render();
}
}
性能特点
- 零 CPU 占用等待:线程在
cv.wait()期间不占用 CPU 资源 - 锁外执行渲染:耗时操作在锁外执行,避免阻塞主线程调度
- 完成标志管理:通过
render_done标志协调主线程与渲染线程
动态尺寸调整
支持运行时动态调整渲染尺寸:
void OffscreenRenderContext::SetSize(double width, double height) {
this->width = width;
this->height = height;
this->is_reset_size.store(true);
}
bool OffscreenRenderContext::Bind() {
if (is_reset_size.load()) {
// 重新分配纹理内存
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, this->width, this->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// 重置纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
// ...
}
技术要点总结
| 要点 | 说明 |
|---|---|
| 资源共享 | 通过 eglCreateContext 的第三个参数实现与主上下文共享资源 |
| Pbuffer Surface | 创建最小尺寸的表面,实际渲染使用 FBO |
| FBO 附件 | 将纹理作为颜色附件附加到 FBO,实现离屏绘制 |
| 线程安全 | 使用原子变量和条件变量实现线程间同步 |
| 渲染同步 | glFinish() 确保渲染完成后再通知主线程 |
| 尺寸适配 | 支持动态调整渲染尺寸,自动重新分配纹理内存 |
注意事项
- 线程生命周期管理:析构函数中必须设置停止标志并唤醒线程,避免线程泄漏
- EGL 上下文绑定:每个线程只能绑定一个 EGL 上下文,不可跨线程使用
- 资源清理:销毁时按正确顺序清理 FBO、纹理、Surface 和 Context
- 错误检查:初始化阶段要检查 FBO 是否完整(
GL_FRAMEBUFFER_COMPLETE) - 性能优化:耗时渲染操作必须在锁外执行,避免阻塞主线程
二、Vsync调度渲染并合成上屏
VSync(垂直同步)机制不仅是屏幕刷新的基准,也是驱动渲染流水线的心跳。在本架构中,VSync 线程作为核心调度器,负责监听硬件信号、协调多线程任务队列,并最终将渲染结果合成上屏。
首先可查看HarmonyOS官方文档,学习VSync的使用方式
1. 调度架构设计
主线程通过创建一个独立的 VSync 监听循环,实现了渲染与屏幕刷新的精准同步。整个调度流程由 MultiThreadRenderManager 统筹,分为以下三个阶段:
- 监听阶段:利用
OH_NativeVSyncAPI 注册回调,构建一个独立的 VSync 信号循环线程。 - 分发阶段:收到信号后,主线程检查线程池状态,挑选空闲的子线程并分发渲染任务。
- 合成阶段:获取子线程渲染完成的纹理,通过 GPU 直接拷贝至屏幕缓冲区。
2. VSync 信号循环的构建
VSync 线程的构建依赖于 OH_NativeVSync_Create 和 OH_NativeVSync_RequestFrame。关键在于“请求-回调-再请求”的循环模式,这确保了每次屏幕刷新都能触发一次渲染流程。
// 启动渲染,实际上是启动 VSync 信号循环
void MultiThreadRenderManager::StartRender(RenderDrawCallback render_draw_callback, void* data) {
this->render_draw_callback = render_draw_callback;
this->data = data;
// 创建 VSync 实例,名称为 "VsyncManager"
if (vsync == nullptr) {
vsync = OH_NativeVSync_Create("VsyncManager", 12);
}
// 关键点:发起第一次请求,传入回调函数和 this 指针
// 这将启动整个渲染循环
OH_NativeVSync_RequestFrame(vsync, MultiThreadRenderManager::RenderVsyncCallback, this);
}
3. 信号回调与任务流转
当硬件产生 VSync 中断时,RenderVsyncCallback 会在独立的上下文中被调用。该函数不仅是信号的入口,更是连接上一帧与下一帧的桥梁。
// 静态回调函数:由 VSync 信号触发
void MultiThreadRenderManager::RenderVsyncCallback(long long, void *data) {
MultiThreadRenderManager* manager = (MultiThreadRenderManager*)data;
// 1. 执行具体的渲染调度逻辑
if (manager != nullptr) {
manager->Render();
}
// 2. 循环关键:立即请求下一帧信号
// 这保证了只要应用不停止,每一帧 VSync 都会被捕捉到
OH_NativeVSync_RequestFrame(manager->vsync, MultiThreadRenderManager::RenderVsyncCallback, manager);
}
4. 轮询调度与并行执行
在 Render() 方法中,主线程通过**轮询(Round-Robin)**策略,将任务依次分配给线程池中的子线程。这种设计最大化了多核利用率,避免了单线程过载。
核心逻辑流程如下:
- 状态检查:通过
IsDoneAndSet原子操作,检查当前索引对应的子线程是否已完成上一帧的绘制。 - 结果上屏:如果子线程已就绪,将其生成的纹理 ID 传入
DrawOffscreenResult进行合成。 - 任务分发:调用
RunRender()唤醒子线程的条件变量,使其开始绘制下一帧。 - 索引轮转:更新索引,指向下一个子线程,确保负载均衡。
void MultiThreadRenderManager::Render() {
// 选取当前轮到的线程
auto render_thread = render_threads[render_index];
if (render_thread != nullptr && render_thread->IsDoneAndSet()) {
// 1. --- 合成上屏 ---
// 将子线程在 FBO 中绘制好的纹理拷贝到屏幕
DrawOffscreenResult(render_thread->GetTextureId());
// 2. --- 唤醒子线程 ---
// 解除子线程的 wait 状态,让它开始画下一帧
render_thread->RunRender();
// 3. --- 轮询索引更新 ---
render_index++;
if (render_index >= render_threads.size()) {
render_index = 0; // 循环回到第一个线程
}
} else {
// 如果子线程未完成,记录丢帧,但不阻塞主线程
is_render_success.store(false);
}
}
5. 高效合成:glBlitFramebuffer
合成上屏是将离屏纹理转化为可见图像的最后一步。使用 glBlitFramebuffer 可以利用 GPU 的拷贝能力,比传统的绘制全屏矩形效率更高。
void MultiThreadRenderManager::DrawOffscreenResult(GLuint tex) {
// 设置视口和清空屏幕
glViewport(0, 0, width, height);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
// 1. 绑定读取缓冲区,并附加子线程的纹理
glBindFramebuffer(GL_READ_FRAMEBUFFER, read_buffer);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
// 2. 绑定绘制缓冲区为默认屏幕 (0)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// 3. 执行像素拷贝
glBlitFramebuffer(0, 0, width, height, // 源矩形
0, 0, width, height, // 目标矩形
GL_COLOR_BUFFER_BIT, // 拷贝内容
GL_NEAREST); // 缩放过滤方式
// 4. 交换前后缓冲区,将图像呈现在屏幕上
eglSwapBuffers(display, surface);
}
6. 技术要点总结
| 要点 | 说明 |
|---|---|
| 信号循环 | 在回调末尾再次调用 RequestFrame,形成永不间断的 VSync 监听循环。 |
| 非阻塞设计 | 主线程只负责分发和合成,子线程负责耗时绘制,互不阻塞。 |
| 负载均衡 | 通过 render_index 循环递增,确保渲染任务均匀分布在所有子线程中。 |
| GPU 加速合成 | 使用 glBlitFramebuffer 替代 glDrawElements 进行上屏,降低 CPU 开销。 |
| 动态伸缩 | 支持运行时调整线程数量,通过 SetThreadCount 动态扩容或缩减线程池。 |
三、napi桥接与绘制管理
为了实现 ArkTS (UI 层) 与 Native C++ (渲染层) 之间的高效交互,本案例设计了一套基于 NAPI (Native API) 的桥接机制。该机制负责传递 Surface ID、管理渲染生命周期、动态调整线程数,并利用线程安全函数将底层的性能数据实时回传给上层。
1. 架构概览
桥接层主要由 ControllerBridge(静态接口层)和 RenderController(逻辑控制层)组成:
- ControllerBridge:作为 ArkTS 的入口,通过静态映射表管理多个
RenderController实例,支持多窗口并发渲染。 - RenderController:持有一个
MultiThreadRenderManager实例,负责具体的渲染逻辑、Shader 管理以及与 JS 层的数据通信。
2. 生命周期管理与初始化
Native 渲染依赖于 XComponent 组件提供的 OHNativeWindow。初始化流程包含创建 Native Window、构建渲染控制器以及启动渲染线程。
// 1. 从 XComponent ID 获取 Native Window 句柄
OHNativeWindow *nativeWindow;
OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, &nativeWindow);
// 2. 创建并保存 RenderController 实例
renderController = new RenderController(surfaceId);
render_ctr[surfaceId] = renderController;
// 3. 初始化窗口、设置尺寸并启动渲染循环
renderController->SetWindowSize(width, height);
renderController->InitNativeWindow(nativeWindow); // 内部创建 EGL Context 和 Manager
renderController->SetIsDark(isDark);
3. 动态线程数调整
为了演示多线程优化的效果,桥接层暴露了 SetThreadCount 接口。UI 层可以通过滑动条动态调整线程数,底层会实时增删子线程池。
// 接收 ArkTS 传来的线程数 (1-15)
napi_value ControllerBridge::SetThreadCount(napi_env env, napi_callback_info info) {
// 解析参数:surfaceId 和 count
int64_t surfaceId = ...;
int32_t count = ...;
// 获取对应的 Controller 并设置
auto renderController = GetRenderController(surfaceId);
if (renderController) {
renderController->SetThreadCount(count); // 最终调用 manager->SetThreadCount
}
return nullptr;
}
4. 线程安全的数据回传
渲染线程运行在 C++ 后台,若要更新 UI 上的 FPS 或耗时信息,必须使用 NAPI 的 ThreadSafeFunction 机制,以避免跨线程访问 JS 环境导致的崩溃。RenderRuntimeInfo 类封装了这一逻辑。
4.1 初始化线程安全函数
在构造函数中,我们将 JS 传入的回调对象包装成线程安全句柄。
RenderRuntimeInfo::RenderRuntimeInfo(napi_env env, napi_value renderInfoObj) {
// 1. 持久化 JS 对象引用,防止被 GC
napi_create_reference(env, renderInfoObj, 1, &objectRef_);
// 2. 创建一个空函数作为 TSFN 的占位回调
napi_create_function(env, "Placeholder", ... , [](napi_env, napi_callback_info) { return nullptr; }, nullptr, &empty_function);
// 3. 初始化线程安全函数
napi_create_threadsafe_function(
env,
empty_function,
nullptr,
resource_name,
0,
1,
nullptr,
nullptr,
this, // 上下文指针
OnJsThread, // JS 线程执行的回调
&tsfn_ // 输出句柄
);
}
4.2 跨线程发送数据
当 C++ 渲染线程计算出 FPS 或耗时后,调用 SendToJs 将数据投递到 JS 队列。
void RenderRuntimeInfo::SetFPS(int fps) {
SendToJs(3, fps); // Type 3 代表 FPS
}
void RenderRuntimeInfo::SendToJs(int type, int value) {
// 分配数据包
CallbackData* data = new CallbackData();
data->type = type;
data->value = value;
// 非阻塞调用,将数据发送到 JS 线程
napi_call_threadsafe_function(tsfn_, data, napi_tsfn_nonblocking);
}
4.3 JS 线程执行回调
OnJsThread 会在主线程(JS 线程)被自动调用。它负责解析数据包,还原 JS 对象,并调用对应的 ArkTS 方法。
void RenderRuntimeInfo::OnJsThread(napi_env env, napi_value jsCallback, void* context, void* data) {
auto instance = static_cast<RenderRuntimeInfo*>(context);
auto callbackData = static_cast<CallbackData*>(data);
// 1. 还原 JS 对象
napi_value renderInfoObj;
napi_get_reference_value(env, instance->objectRef_, &renderInfoObj);
// 2. 根据 Type 映射方法名
const char* methodName = "";
switch (callbackData->type) {
case 0: methodName = "setThreadCount"; break;
case 1: methodName = "setCurrentFrameDrawTime"; break;
case 2: methodName = "setMainThreadDrawTime"; break;
case 3: methodName = "setFPS"; break;
}
// 3. 获取并调用 JS 对象的方法
napi_value methodFunc;
napi_get_named_property(env, renderInfoObj, methodName, &methodFunc);
napi_value argv[1];
napi_create_int32(env, callbackData->value, &argv[0]);
// 执行 JS 回调
napi_call_function(env, renderInfoObj, methodFunc, 1, argv, nullptr);
// 4. 清理数据
delete callbackData;
}
5. 性能监控与 FPS 计算
为了准确评估多线程渲染的效果,RenderRuntimeInfo 内部维护了一个环形缓冲区,记录每帧的完成时间和状态。
- 数据记录:每帧渲染结束时,记录时间戳和是否成功绘制。
- 滑动窗口计算:保存最近 120 帧的数据,计算这段时间内的成功帧数与总时间的比值,从而得出平滑的 FPS 值。
void RenderRuntimeInfo::SetCurrentFrameDrawFinished(int time, bool finished) {
// 记录当前帧状态
current_frame_draw_finished_.push_back({time, finished});
if (current_frame_draw_finished_.size() >= 120) {
current_frame_draw_finished_.erase(current_frame_draw_finished_.begin());
}
// 触发 FPS 更新
fps = CalculateCurrentFps();
}
int RenderRuntimeInfo::CalculateCurrentFps() {
if (current_frame_draw_finished_.size() < 2) return 0;
long long start_time = current_frame_draw_finished_.front().time;
long long end_time = current_frame_draw_finished_.back().time;
long long duration_ms = end_time - start_time;
if (duration_ms <= 0) return 0;
// 统计成功帧数
int finished_count = 0;
for (const auto& info : current_frame_draw_finished_) {
if (info.finished) finished_count++;
}
return (static_cast<double>(finished_count) * 1000.0) / duration_ms;
}
6. 绘制回调实现
RenderController 中的 Run 方法向 MultiThreadRenderManager 注册了绘制回调。该回调在子线程中执行,包含清屏、设置 Uniform 变量、绘制几何体以及模拟耗时操作(用于演示多线程效果)。
manager->StartRender([](void *data) {
RenderController *controller = (RenderController *)data;
// 初始化 Shader 和 程序对象
if (!controller->is_init) {
controller->mProgramHandle = controller->CreateProgram(...);
controller->is_init = true;
}
// 执行绘制命令
glViewport(0, 0, controller->windowWidth, controller->windowHeight);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(controller->mProgramHandle);
// 更新 Uniforms (时间、分辨率、深色模式)
glUniform1f(controller->iTimeHandle, controller->iTime);
glUniform3f(controller->resolutionHandle, width, height, 1.0f);
glUniform1f(controller->isDarkHandle, controller->is_dark);
// 绘制矩形
glVertexAttribPointer(controller->positionHandle, 2, GL_FLOAT, GL_FALSE, 2 * 4, shader::squareVerticles);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// === 模拟耗时操作 ===
// 验证多线程加速效果:如果单线程耗时过长,增加线程数可提升帧率
while (true) {
auto end_time = ...;
if (end_time - start_time > 72) break; // 模拟约 72ms 的计算/渲染压力
}
}, this);
四、ArkTS UI 层实现与交互
UI 层是用户与应用交互的直接窗口。在本案例中,ArkTS 界面不仅负责展示离屏渲染的结果,
还提供了实时调整渲染参数的控件,并将底层的性能数据(FPS、耗时等)可视化地呈现给用户。
本章节将详细解析基于 XComponent 的页面构建与交互逻辑。
1. XComponent 控制器封装
为了更方便地管理 Native 渲染的生命周期,我们封装了一个继承自 XComponentController 的 MyXComponentController。该控制器监听 Surface 的创建与销毁事件,并同步调用 NAPI 接口通知 C++ 层。
class MyXComponentController extends XComponentController {
private onSurfaceId: (id: string) => void;
constructor(onSurfaceId: (id: string) => void) {
super();
this.onSurfaceId = onSurfaceId;
}
// Surface 创建完成回调
onSurfaceCreated(surfaceId: string): void {
this.onSurfaceId(surfaceId);
}
// Surface 销毁回调,通知 C++ 层清理资源
onSurfaceDestroyed(surfaceId: string): void {
Napi.surfaceDestroyed(BigInt(surfaceId));
}
}
2. 组件状态与数据管理
主页面 Index 使用 @ComponentV2 装饰器(HarmonyOS 新版状态管理),并定义了响应式变量来管理渲染状态和性能数据:
sufId:存储底层 Surface 的唯一 ID,用于 NAPI 调用。runtimeInfo:自定义类实例,用于接收 C++ 线程安全函数回传的 FPS 和耗时数据。isDarkMode:监听系统深色模式变化,并同步更新 Native 层的渲染背景。
@Entry
@ComponentV2
struct Index {
@Local _size: Size | undefined = undefined;
@Local sufId: bigint | undefined = undefined;
// 性能监控对象
@Local runtimeInfo: MyRenderRuntimeInfo = new MyRenderRuntimeInfo();
// 深色模式监听
listener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync('(dark-mode: true)');
@Local isDarkMode: boolean = this.listener.matches;
// ... controller 初始化
}
3. Native 初始化与生命周期绑定
在 MyXComponentController 的回调中,我们完成了 Native 层的初始化流程。当 XComponent 创建出底层的 Surface 后,立即调用 initSurfaceId 启动渲染线程。
@Local controller: MyXComponentController = new MyXComponentController((id) => {
this.sufId = BigInt(id);
// 1. 初始化 Native 渲染环境,传入尺寸和深色模式状态
Napi.initSurfaceId(
this.sufId,
this._size!!.width,
this._size!!.height,
this.isDarkMode
);
// 2. 将 JS 对象传递给 C++,用于线程安全的数据回传
Napi.setRuntimeInfo(this.sufId, this.runtimeInfo);
});
同时,监听系统主题变化,实时通知 C++ 层更新 Shader 中的 Uniform 变量:
aboutToAppear(): void {
this.listener.on("change", (matches) => {
this.isDarkMode = matches.matches;
// 动态切换深色模式
if (this.sufId) {
Napi.setDarkMode(this.sufId, this.isDarkMode);
}
})
}
4. 实时性能数据展示
页面底部使用 Column 布局实时展示 runtimeInfo 中的数据。由于 runtimeInfo 的属性通过 NAPI 线程安全函数更新,这些数据会自动触发 UI 刷新。
Column({space: 14}) {
Text(`过一段时间后会120帧变成60帧,是因为系统动态调整帧率...`)
.width("100%").textAlign(TextAlign.Start)
Text(`使用while死循环模拟卡顿72ms...`)
.width("100%").textAlign(TextAlign.Start)
// 数据展示
Text(`绘制线程数量: ${this.runtimeInfo.threadCount}`)
.fontWeight(FontWeight.Bold)
Text(`子线程单帧绘制耗时:${this.runtimeInfo.currentFrameDrawTime}ms`)
.fontWeight(FontWeight.Bold)
Text(`主线程单帧绘制耗时:${this.runtimeInfo.mainThreadDrawTime}ms`)
.fontWeight(FontWeight.Bold)
Text(`fps: ${this.runtimeInfo.fps}`)
.fontWeight(FontWeight.Bold)
}
5. 动态线程数调节
为了直观演示多线程并行渲染对性能的提升,页面提供了一个 Slider 组件,允许用户在运行时动态调整渲染线程数(1-10)。
Slider({
value: this.runtimeInfo.threadCount,
min: 1,
max: 10,
step: 1,
style: SliderStyle.InSet,
})
.showTips(true, this.runtimeInfo.threadCount + "")
.onChange((value) => {
// 滑动时直接调用 NAPI 接口,C++ 层会动态扩容或缩减线程池
Napi.setThreadCount(this.sufId, value);
})
预期效果:
- 1 个线程时:由于 C++ 层模拟了 72ms 的计算耗时,加上绘制开销,单帧总耗时约 74ms。根据 1000 m s / 74 m s ≈ 13.5 F P S 1000ms / 74ms \approx 13.5 FPS 1000ms/74ms≈13.5FPS,画面会非常卡顿。
- 10 个线程时:任务被并行分发给 10 个子线程。虽然总计算量不变,但多个子线程同时工作,主线程在单位时间内能调度并合成更多的帧。假设并行度足够,帧率可显著提升,甚至达到屏幕刷新率上限(如 120 FPS)。
6. 布局与尺寸适配
使用 Stack 布局将 XComponent 置于底层,UI 控件悬浮于上层。同时监听 onSizeChange 事件,确保窗口大小变化时,Native 渲染的分辨率与 UI 尺寸保持一致。
Stack({ alignContent: Alignment.Bottom }) {
if (this._size != undefined) {
XComponent({
type: XComponentType.SURFACE,
controller: this.controller
})
.width("100%")
.height("100%")
}
// ... UI 控件层 ...
}
.onSizeChange((_, news) => {
this._size = {
width: vp2px(news.width as number),
height: vp2px(news.height as number)
};
// 通知 C++ 层更新 Surface 尺寸
if (this._size != undefined && this.sufId != undefined) {
Napi.setSurfaceSize(this.sufId, this._size.width, this._size.height);
}
})
总结
本文档深入解析了基于 HarmonyOS Native API 的多线程并行渲染方案。该方案通过 OpenGLES 离屏渲染、多线程并行计算、VSync 信号调度以及 NAPI 跨语言交互技术的深度结合,有效解决了复杂图形场景下的渲染瓶颈问题。
1. 技术架构全景
本案例构建了一个完整的高性能渲染流水线,涵盖从底层图形库到上层 UI 交互的全链路:
- 离屏渲染层:利用 EGL Pbuffer Surface 和 FBO(帧缓冲对象),将渲染目标从屏幕分离,实现了多线程环境下的独立绘图。
- 多线程并行层:设计了“生产者-消费者”模型,主线程作为指挥官,通过轮询策略将任务分发至多个子线程并行执行,最大化利用多核 CPU 性能。
- 调度同步层:基于
OH_NativeVSync实现精准的垂直同步,确保渲染节奏与屏幕刷新一致;利用glBlitFramebuffer实现 GPU 加速的图像合成。 - 交互桥接层:通过 NAPI 和线程安全函数(ThreadSafeFunction),打通了 ArkTS UI 层与 C++ 渲染层的数据壁垒,实现了双向的实时通信。
更多推荐




所有评论(0)