鸿蒙实时模糊与离屏渲染:别再把计算挪到主线程

前言

“相机取景器要实时景深模糊,逐帧绘制会卡主线程,能不能用离屏渲染把模糊丢到 GPU 单独通道?”——这是图形开发里非常典型的问题,但很多回答把“模糊计算”和“主线程阻塞”错误绑定,还给出大量并不存在的伪 API。本文澄清 ArkUI 模糊的真实渲染管线,给出能跑的代码,并指出哪些“方案”是抄不得的。

问题描述

楼主诉求:相机预览界面实时显示景深/动态模糊,手指滑动变焦环时要保持 60fps 跟手,不要阻塞主线程。

常见误解:认为“模糊在主线程算”,所以要手动离屏渲染把计算搬走。事实上阻塞主线程的从来不是模糊本身,而是你在每帧做 CPU 侧像素操作、或者用错了渲染管线。ArkUI 自带的动态模糊接口,本就运行在 GPU 上。

细节解析

ArkUI 渲染流程:主线程生成 UI 描述 → 提交给 RenderService(RS) → RS 在 RenderThread 协调 GPU 执行模糊、上屏。backgroundBlurStylebackgroundEffectblur() 这些接口每帧由 RS 的 RenderThread 调用 GPU 执行,并带缓存(模糊区域和参数不变时复用)。它们天然满足“模糊在 GPU 单独通道”,不需要你手动离屏渲染。

要点:

  • 不要拿 OffscreenCanvas 做实时模糊:它的离屏绘制是 CPU 实现(“绘制速度较慢,性能要求场景应避免”),反而更糟。
  • 真正卡主线程的元凶:在手势回调里做异步模糊(如 effectKit.createEffect)。动态模糊接口是实时渲染接口,参数变化自动重算,跟手性靠 @State 驱动即可。
  • 变焦跟手瓶颈在状态更新频率,不在模糊:用 @State 驱动,ArkUI 状态更新帧对齐;不要把变焦环每帧重建组件/管线。
  • 需要自定义 shader 模糊算法时(API 16+),才走 Native Drawing 的 GPU 离屏:OH_Drawing_GpuContextCreate()(替代废弃的 OH_Drawing_GpuContextCreateFromGL)→ OH_Drawing_SurfaceCreateFromGpuContext 创建离屏 Surface → 前端用 RenderNode.draw 上树。
  • 纠错:社区里出现的 new graphics.Surface() / RenderTarget / graphics.RenderPass 并非 ArkTS 真实 API,按它写会编译失败。
  • 真正的景深需要深度图或主体分割蒙版,单纯高斯模糊只能全局虚化。

示例代码

推荐方案:XComponent 承载预览 + backgroundEffect 做 GPU 模糊

import { XComponent, XComponentType, XComponentController } from '@kit.ArkUI';
import { AdaptiveColor } from '@kit.ArkUI';

@Entry
@Component
struct DepthPreview {
  @State blurRadius: number = 30;
  private xc: XComponentController = new XComponentController();

  build() {
    Stack() {
      // 相机预览:XComponent Surface,GPU 直接合成上屏,主线程零参与
      XComponent({ id: 'cam', type: XComponentType.SURFACE, controller: this.xc })
        .width('100%').height('100%')

      // 景深模糊层:backgroundEffect 走 RS RenderThread GPU 管线,带缓存
      Column().width('100%').height('100%')
        .backgroundEffect({
          radius: this.blurRadius,
          saturation: 1.1,
          brightness: 1.0,
          adaptiveColor: AdaptiveColor.DEFAULT
        })

      // 变焦环交互:@State 驱动,帧对齐提交,不在回调里做异步模糊
      Slider({ value: this.blurRadius, min: 0, max: 60 })
        .onChange((v: number) => { this.blurRadius = v; })
    }
  }
}

需要自定义 GPU 离屏模糊时(API 16+,Native 侧)

#include <native_drawing/drawing_gpu_context.h>
#include <native_drawing/drawing_surface.h>
#include <native_drawing/drawing_canvas.h>

OH_Drawing_GpuContext* gpu = OH_Drawing_GpuContextCreate();
OH_Drawing_Image_Info info = {width, height, COLOR_FORMAT_RGBA_8888, ALPHA_FORMAT_OPAQUE};
// 真正的 GPU 离屏渲染通道
OH_Drawing_Surface* off = OH_Drawing_SurfaceCreateFromGpuContext(gpu, true, info);
OH_Drawing_Canvas* canvas = OH_Drawing_SurfaceGetCanvas(off);
// ... 在 off 上跑自定义高斯 shader ...
OH_Drawing_SurfaceDestroy(off);
OH_Drawing_GpuContextDestroy(gpu);

前端通过 RenderNodedraw 调用 Native 绘制,经 NodeContainer 上树显示。

总结

实时模糊的正确姿势:优先用 ArkUI 自带的 backgroundEffect / backgroundBlurStyle,它们本就跑在 RS 的 GPU 渲染线程、带缓存,单帧约 5.6ms,远低于 16.7ms 的 60fps 预算;跟手性靠 @State 驱动,不要在手势回调里做异步模糊。只有需要自定义 shader 时才走 Native Drawing 的 GPU 离屏(OH_Drawing_* 系列)。记住:ArkUI 模糊不阻塞主线程,阻塞主线程的是你写错的 CPU 像素操作;OffscreenCanvas 是 CPU 实现,别拿来做实时模糊。

Logo

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

更多推荐