鸿蒙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. 渲染流程

离屏渲染的标准流程包括:

  1. 绑定渲染环境
    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;
    }
    
  2. 执行绘制
    void OffscreenRenderContext::Draw() {
        if (render_draw_callback != nullptr) {
            render_draw_callback(data);
        }
    }
    
  3. 解绑并同步
    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() 确保渲染完成后再通知主线程
尺寸适配 支持动态调整渲染尺寸,自动重新分配纹理内存
注意事项
  1. 线程生命周期管理:析构函数中必须设置停止标志并唤醒线程,避免线程泄漏
  2. EGL 上下文绑定:每个线程只能绑定一个 EGL 上下文,不可跨线程使用
  3. 资源清理:销毁时按正确顺序清理 FBO、纹理、Surface 和 Context
  4. 错误检查:初始化阶段要检查 FBO 是否完整(GL_FRAMEBUFFER_COMPLETE
  5. 性能优化:耗时渲染操作必须在锁外执行,避免阻塞主线程

二、Vsync调度渲染并合成上屏

VSync(垂直同步)机制不仅是屏幕刷新的基准,也是驱动渲染流水线的心跳。在本架构中,VSync 线程作为核心调度器,负责监听硬件信号、协调多线程任务队列,并最终将渲染结果合成上屏。

首先可查看HarmonyOS官方文档,学习VSync的使用方式

1. 调度架构设计

主线程通过创建一个独立的 VSync 监听循环,实现了渲染与屏幕刷新的精准同步。整个调度流程由 MultiThreadRenderManager 统筹,分为以下三个阶段:

  • 监听阶段:利用 OH_NativeVSync API 注册回调,构建一个独立的 VSync 信号循环线程。
  • 分发阶段:收到信号后,主线程检查线程池状态,挑选空闲的子线程并分发渲染任务。
  • 合成阶段:获取子线程渲染完成的纹理,通过 GPU 直接拷贝至屏幕缓冲区。
2. VSync 信号循环的构建

VSync 线程的构建依赖于 OH_NativeVSync_CreateOH_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)**策略,将任务依次分配给线程池中的子线程。这种设计最大化了多核利用率,避免了单线程过载。
核心逻辑流程如下:

  1. 状态检查:通过 IsDoneAndSet 原子操作,检查当前索引对应的子线程是否已完成上一帧的绘制。
  2. 结果上屏:如果子线程已就绪,将其生成的纹理 ID 传入 DrawOffscreenResult 进行合成。
  3. 任务分发:调用 RunRender() 唤醒子线程的条件变量,使其开始绘制下一帧。
  4. 索引轮转:更新索引,指向下一个子线程,确保负载均衡。
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 渲染的生命周期,我们封装了一个继承自 XComponentControllerMyXComponentController。该控制器监听 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/74ms13.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++ 渲染层的数据壁垒,实现了双向的实时通信。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐