HarmonyOS 自适应 VRS:用 Vulkan 降低 GPU 渲染负载

什么是 VRS

你玩 3D 游戏的时候有没有注意到,画面中间你盯着看的地方特别清晰,但边缘部分其实没那么精细?人眼就是这样,对中心区域的细节更敏感,对边缘区域不太在意。

VRS(Variable Rate Shading,可变速率着色)就是利用了这个原理。它允许 GPU 对画面的不同区域使用不同的渲染精度:重要的区域(比如你盯着看的地方)用高精度渲染,不重要的区域(比如边缘、背景)用低精度渲染。

自适应 VRS 更进一步:它会自动分析画面内容,智能决定哪些区域需要高精度渲染,哪些可以降低精度。这样既保证了视觉效果,又降低了 GPU 的负载,提高了帧率。

环境搭建

硬件要求

  • 设备类型:请参考 XEngine 开发指南的硬件要求

软件要求

  • DevEco Studio 版本:DevEco Studio 6.0.0 Release 及以上
  • HarmonyOS SDK 版本:HarmonyOS 6.0.0 Release SDK 及以上

搭建步骤

  1. 安装 DevEco Studio:去华为开发者官网下载安装
  2. 配置开发环境:确保网络环境正常
  3. 设备调试:使用真机进行调试

项目结构

├── entry/src/main	         // 代码区
│  ├── cpp
│  │  ├── types
│  │  │  ├── libnativerender
│  │  │     └── index.d.ts      // native层接口注册文件 
│  │  │── napi_init.cpp          // native api层接口的具体实现函数
│  │  │── CMakeLists.txt         // native层编译配置
│  │  │── 3rdParty               // 三方件
│  │  │── common                 // 通用接口  
│  │  │── file                   // 文件管理
│  │  │── libs                   // 三方动态库
│  │  │── manager                // native&arkts交互
│  │  │── render                 // 渲染
│  │  │── vulkanbase             // vulkan基础能力封装
│  ├── ets	
│  │  ├── entryability 
│  │  │    └── EntryAbility.ts   // 程序入口类
│  │  ├── pages 
│  │  │    └── index.ets         // 主界面展示类
│  ├── resources                 // 资源文件目录
│  │  ├── base
│  │  │  ├── media
│  │  │     └── icon.png         // 图片资源
│  │  ├── rawfile 
│  │  │  ├── Sponza             
│  │  │      └── sponza.obj      // 模型资源

这个项目使用 Vulkan 图形 API,代码主要在 C++ 层。vulkanbase 目录里封装了 Vulkan 的基础能力,render 目录里是渲染相关的代码。

第一步:引入头文件

#include <string>
#include <vector>
#include <algorithm>
#include <xengine/xeg_vulkan_extension.h>
#include <xengine/xeg_vulkan_adaptive_vrs.h>

这些头文件的作用:

  • stringvectoralgorithm:C++ 标准库,处理字符串、数组、查找等
  • xeg_vulkan_extension.h:XEngine 的 Vulkan 扩展接口,用来查询设备支持哪些扩展
  • xeg_vulkan_adaptive_vrs.h:自适应 VRS 的核心接口,创建实例、生成着色率纹理等

第二步:配置 CMakeLists.txt

find_library(
    xengine-lib
    xengine
)
find_library(
    EGL-lib
    EGL
)
find_library(
    Vulkan-lib
    vulkan
)

target_link_libraries(nativerender PUBLIC
${EGL-lib} ${Vulkan-lib} ${xengine-lib})

这里链接了三个库:

  • xengine:XEngine 的核心库,自适应 VRS 的功能就在这个库里
  • EGL:OpenGL ES 的窗口系统接口
  • vulkan:Vulkan 图形 API 的库

第三步:查询设备是否支持自适应 VRS

在使用自适应 VRS 之前,必须先检查设备是否支持这个功能。就像你买电器之前要先看看家里有没有对应的插座。

VkPhysicalDevice physicalDevice;
std::vector<std::string> supportedExtensions;
uint32_t pPropertyCount;

先定义几个变量:

  • physicalDevice:Vulkan 物理设备,代表你的 GPU
  • supportedExtensions:存储支持的扩展名称
  • pPropertyCount:扩展的数量
HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice, &pPropertyCount, nullptr);
if (pPropertyCount > 0) {
    std::vector<XEG_ExtensionProperties> pProperties(pPropertyCount);
    if (HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice, &pPropertyCount, &pProperties.front()) == VK_SUCCESS) {
        for (auto ext : pProperties) {
            supportedExtensions.push_back(ext.extensionName);
        }
    }
}

调用 HMS_XEG_EnumerateDeviceExtensionProperties 查询设备支持的扩展。这个函数调用两次:

  1. 第一次传 nullptr,只获取扩展数量(pPropertyCount
  2. 第二次传入数组,获取所有扩展的详细信息

这是 Vulkan 的标准用法,很多 Vulkan API 都是这种"先问数量,再取数据"的模式。

if (std::find(supportedExtensions.begin(), supportedExtensions.end(), XEG_ADAPTIVE_VRS_EXTENSION_NAME) == supportedExtensions.end()) {
    exit(1); // return error
}

std::find 在支持的扩展列表里查找自适应 VRS 的扩展名。如果找不到,说明设备不支持,直接退出程序。

第四步:创建自适应 VRS 实例

XEG_AdaptiveVRS xeg_adaptiveVRS;

先声明一个实例句柄。这个句柄就像一个"遥控器",后面所有的 VRS 操作都要通过它。

int m_renderWidth;
int m_renderHeight;
int VRS_TILE_SIZE;
VkDevice device;
XEG_AdaptiveVRSCreateInfo xeg_createInfo;
XEG_AdaptiveVRSDescription xeg_description;

定义一些变量:

  • m_renderWidthm_renderHeight:渲染的宽高
  • VRS_TILE_SIZE:VRS 的分片大小。画面会被分成很多小块,每块独立决定渲染精度
  • device:Vulkan 逻辑设备
  • xeg_createInfo:创建 VRS 实例所需的参数
  • xeg_description:下发着色率纹理命令所需的参数
VkExtent2D inputSize;
inputSize.width = m_renderWidth;
inputSize.height = m_renderHeight;

VkRect2D inputRegion {};
inputRegion.extent.width = m_renderWidth;
inputRegion.extent.height = m_renderHeight;
inputRegion.offset.x = 0;
inputRegion.offset.y = 0;

设置输入尺寸和区域:

  • inputSize:上一帧渲染结果的图像尺寸
  • inputRegion:输入纹理的区域,从 (0,0) 开始,大小等于渲染宽高
xeg_createInfo.inputSize = inputSize;
xeg_createInfo.inputRegion = inputRegion;
xeg_createInfo.adaptiveTileSize = VRS_TILE_SIZE;
xeg_createInfo.errorSensitivity = 0.5;
xeg_createInfo.flip = false;

配置创建参数:

  • inputSize:输入图像的尺寸
  • inputRegion:输入图像的区域
  • adaptiveTileSize:分片大小,越小精度越高,但计算量也越大
  • errorSensitivity:误差敏感度,0.5 是一个平衡点。值越小,越倾向于高精度渲染;值越大,越倾向于低精度渲染
  • flip:是否翻转图像。false 表示不翻转
HMS_XEG_CreateAdaptiveVRS(device, &xeg_createInfo, &xeg_adaptiveVRS);

最后调用 HMS_XEG_CreateAdaptiveVRS 创建实例。如果创建成功,xeg_adaptiveVRS 就被初始化了。

第五步:生成着色率纹理

这是自适应 VRS 的核心步骤:根据上一帧的渲染结果,生成一个"着色率纹理",告诉 GPU 每个区域应该用什么精度渲染。

VkImageView inputColorImageView = VK_NULL_HANDLE;
VkImageView inputDepthImageView = VK_NULL_HANDLE;
VkImageView outputShadingRateImage = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;

定义三个图像视图和一个命令缓冲区:

  • inputColorImageView:上一帧的颜色附件,就是渲染出来的彩色画面
  • inputDepthImageView:上一帧的深度附件,记录了每个像素离相机有多远
  • outputShadingRateImage:输出的着色率纹理,GPU 会根据这个纹理决定每个区域的渲染精度
  • commandBuffer:Vulkan 命令缓冲区,用来记录和执行 GPU 命令
xeg_description.inputColorImage = inputColorImageView; 
xeg_description.inputDepthImage = inputDepthImageView;
xeg_description.outputShadingRateImage = outputShadingRateImage;
xeg_description.reprojectionMatrix = nullptr;

HMS_XEG_CmdDispatchAdaptiveVRS(commandBuffer, xeg_adaptiveVRS, &xeg_description);

把参数填入 xeg_description,然后调用 HMS_XEG_CmdDispatchAdaptiveVRS 下发命令。

这个函数会分析上一帧的颜色和深度信息,然后生成着色率纹理。分析的逻辑大概是:

  • 颜色变化剧烈的区域(比如物体边缘):保持高精度
  • 颜色变化平缓的区域(比如纯色背景):降低精度
  • 深度变化大的区域(比如前景和背景交界处):保持高精度

reprojectionMatrix 是重投影矩阵,用于处理相机运动时的对齐。如果相机不动,可以传 nullptr

第六步:销毁实例

HMS_XEG_DestroyAdaptiveVRS(xeg_adaptiveVRS);

不需要 VRS 功能时,调用 HMS_XEG_DestroyAdaptiveVRS 销毁实例,释放内存资源。这就像用完遥控器要关机一样。

自适应 VRS 的整体工作流程如下:

查询设备是否支持自适应VRS

创建自适应VRS实例

配置输入尺寸和分片大小

设置误差敏感度

每帧获取上一帧渲染结果

调用 CmdDispatchAdaptiveVRS

分析颜色变化和深度变化

生成着色率纹理

GPU 根据着色率渲染下一帧

自适应 VRS 的工作原理

让我用更通俗的语言解释整个流程:

  1. 渲染上一帧:GPU 正常渲染一帧画面,得到颜色图和深度图
  2. 分析画面:自适应 VRS 分析颜色图和深度图,找出哪些区域需要高精度,哪些可以降低精度
  3. 生成着色率纹理:根据分析结果,生成一个纹理,每个区域标记一个渲染精度等级
  4. 渲染下一帧:GPU 在渲染下一帧时,根据着色率纹理,对不同区域使用不同的渲染精度

这个过程是每帧自动进行的,开发者只需要设置好参数,剩下的交给 XEngine 处理。

画面分析的决策逻辑如下:

是 如物体边缘

否 如纯色背景

是 如前后景交界

输入颜色图和深度图

按分片大小划分区域

分析每个区域的颜色变化

分析每个区域的深度变化

颜色变化是否剧烈?

深度变化是否剧烈?

保持高精度渲染

降低渲染精度

输出着色率纹理

适用场景

自适应 VRS 特别适合以下场景:

  • 3D 游戏:画面复杂,有很多区域可以降低精度
  • VR/AR 应用:对帧率要求极高,VRS 可以有效降低 GPU 负载
  • 图形渲染应用:需要稳定帧率的场景

注意事项

  1. 设备支持:不是所有设备都支持自适应 VRS,一定要先查询设备扩展
  2. 分片大小VRS_TILE_SIZE 要根据你的应用来设置。太小精度高但计算量大,太大精度低但性能好
  3. 误差敏感度errorSensitivity 要根据画面质量要求来调整。值越小画质越好,值越大性能越好
  4. 重投影矩阵:如果相机在移动,需要传入正确的重投影矩阵,否则着色率纹理会和画面对不上

总结

自适应 VRS 是一个很实用的图形优化技术,它能在不明显影响画质的情况下,显著降低 GPU 的渲染负载。核心流程:

  1. 查询设备是否支持自适应 VRS
  2. 创建自适应 VRS 实例,配置参数
  3. 每帧调用 HMS_XEG_CmdDispatchAdaptiveVRS 生成着色率纹理
  4. GPU 根据着色率纹理渲染下一帧
  5. 不需要时销毁实例

掌握了这些,你就能在 HarmonyOS 应用中实现自适应 VRS,让你的 3D 应用跑得更流畅。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐