
HarmonyOS 自适应 VRS用 Vulkan 降低 GPU 渲染负载什么是 VRS你玩 3D 游戏的时候有没有注意到画面中间你盯着看的地方特别清晰但边缘部分其实没那么精细人眼就是这样对中心区域的细节更敏感对边缘区域不太在意。VRSVariable Rate Shading可变速率着色就是利用了这个原理。它允许 GPU 对画面的不同区域使用不同的渲染精度重要的区域比如你盯着看的地方用高精度渲染不重要的区域比如边缘、背景用低精度渲染。自适应 VRS 更进一步它会自动分析画面内容智能决定哪些区域需要高精度渲染哪些可以降低精度。这样既保证了视觉效果又降低了 GPU 的负载提高了帧率。环境搭建硬件要求设备类型请参考 XEngine 开发指南的硬件要求软件要求DevEco Studio 版本DevEco Studio 6.0.0 Release 及以上HarmonyOS SDK 版本HarmonyOS 6.0.0 Release SDK 及以上搭建步骤安装 DevEco Studio去华为开发者官网下载安装配置开发环境确保网络环境正常设备调试使用真机进行调试项目结构├── entry/src/main // 代码区 │ ├── cpp │ │ ├── types │ │ │ ├── libnativerender │ │ │ └── index.d.ts // native层接口注册文件 │ │ │── napi_init.cpp // native api层接口的具体实现函数 │ │ │── CMakeLists.txt // native层编译配置 │ │ │── 3rdParty // 三方件 │ │ │── common // 通用接口 │ │ │── file // 文件管理 │ │ │── libs // 三方动态库 │ │ │── manager // nativearkts交互 │ │ │── 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目录里是渲染相关的代码。第一步引入头文件#includestring#includevector#includealgorithm#includexengine/xeg_vulkan_extension.h#includexengine/xeg_vulkan_adaptive_vrs.h这些头文件的作用string、vector、algorithmC 标准库处理字符串、数组、查找等xeg_vulkan_extension.hXEngine 的 Vulkan 扩展接口用来查询设备支持哪些扩展xeg_vulkan_adaptive_vrs.h自适应 VRS 的核心接口创建实例、生成着色率纹理等第二步配置 CMakeLists.txtfind_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})这里链接了三个库xengineXEngine 的核心库自适应 VRS 的功能就在这个库里EGLOpenGL ES 的窗口系统接口vulkanVulkan 图形 API 的库第三步查询设备是否支持自适应 VRS在使用自适应 VRS 之前必须先检查设备是否支持这个功能。就像你买电器之前要先看看家里有没有对应的插座。VkPhysicalDevice physicalDevice;std::vectorstd::stringsupportedExtensions;uint32_tpPropertyCount;先定义几个变量physicalDeviceVulkan 物理设备代表你的 GPUsupportedExtensions存储支持的扩展名称pPropertyCount扩展的数量HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,pPropertyCount,nullptr);if(pPropertyCount0){std::vectorXEG_ExtensionPropertiespProperties(pPropertyCount);if(HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,pPropertyCount,pProperties.front())VK_SUCCESS){for(autoext:pProperties){supportedExtensions.push_back(ext.extensionName);}}}调用HMS_XEG_EnumerateDeviceExtensionProperties查询设备支持的扩展。这个函数调用两次第一次传nullptr只获取扩展数量pPropertyCount第二次传入数组获取所有扩展的详细信息这是 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 操作都要通过它。intm_renderWidth;intm_renderHeight;intVRS_TILE_SIZE;VkDevice device;XEG_AdaptiveVRSCreateInfo xeg_createInfo;XEG_AdaptiveVRSDescription xeg_description;定义一些变量m_renderWidth、m_renderHeight渲染的宽高VRS_TILE_SIZEVRS 的分片大小。画面会被分成很多小块每块独立决定渲染精度deviceVulkan 逻辑设备xeg_createInfo创建 VRS 实例所需的参数xeg_description下发着色率纹理命令所需的参数VkExtent2D inputSize;inputSize.widthm_renderWidth;inputSize.heightm_renderHeight;VkRect2D inputRegion{};inputRegion.extent.widthm_renderWidth;inputRegion.extent.heightm_renderHeight;inputRegion.offset.x0;inputRegion.offset.y0;设置输入尺寸和区域inputSize上一帧渲染结果的图像尺寸inputRegion输入纹理的区域从 (0,0) 开始大小等于渲染宽高xeg_createInfo.inputSizeinputSize;xeg_createInfo.inputRegioninputRegion;xeg_createInfo.adaptiveTileSizeVRS_TILE_SIZE;xeg_createInfo.errorSensitivity0.5;xeg_createInfo.flipfalse;配置创建参数inputSize输入图像的尺寸inputRegion输入图像的区域adaptiveTileSize分片大小越小精度越高但计算量也越大errorSensitivity误差敏感度0.5 是一个平衡点。值越小越倾向于高精度渲染值越大越倾向于低精度渲染flip是否翻转图像。false 表示不翻转HMS_XEG_CreateAdaptiveVRS(device,xeg_createInfo,xeg_adaptiveVRS);最后调用HMS_XEG_CreateAdaptiveVRS创建实例。如果创建成功xeg_adaptiveVRS就被初始化了。第五步生成着色率纹理这是自适应 VRS 的核心步骤根据上一帧的渲染结果生成一个着色率纹理告诉 GPU 每个区域应该用什么精度渲染。VkImageView inputColorImageViewVK_NULL_HANDLE;VkImageView inputDepthImageViewVK_NULL_HANDLE;VkImageView outputShadingRateImageVK_NULL_HANDLE;VkCommandBuffer commandBufferVK_NULL_HANDLE;定义三个图像视图和一个命令缓冲区inputColorImageView上一帧的颜色附件就是渲染出来的彩色画面inputDepthImageView上一帧的深度附件记录了每个像素离相机有多远outputShadingRateImage输出的着色率纹理GPU 会根据这个纹理决定每个区域的渲染精度commandBufferVulkan 命令缓冲区用来记录和执行 GPU 命令xeg_description.inputColorImageinputColorImageView;xeg_description.inputDepthImageinputDepthImageView;xeg_description.outputShadingRateImageoutputShadingRateImage;xeg_description.reprojectionMatrixnullptr;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 的工作原理让我用更通俗的语言解释整个流程渲染上一帧GPU 正常渲染一帧画面得到颜色图和深度图分析画面自适应 VRS 分析颜色图和深度图找出哪些区域需要高精度哪些可以降低精度生成着色率纹理根据分析结果生成一个纹理每个区域标记一个渲染精度等级渲染下一帧GPU 在渲染下一帧时根据着色率纹理对不同区域使用不同的渲染精度这个过程是每帧自动进行的开发者只需要设置好参数剩下的交给 XEngine 处理。画面分析的决策逻辑如下是 如物体边缘否 如纯色背景是 如前后景交界否输入颜色图和深度图按分片大小划分区域分析每个区域的颜色变化分析每个区域的深度变化颜色变化是否剧烈?深度变化是否剧烈?保持高精度渲染降低渲染精度输出着色率纹理适用场景自适应 VRS 特别适合以下场景3D 游戏画面复杂有很多区域可以降低精度VR/AR 应用对帧率要求极高VRS 可以有效降低 GPU 负载图形渲染应用需要稳定帧率的场景注意事项设备支持不是所有设备都支持自适应 VRS一定要先查询设备扩展分片大小VRS_TILE_SIZE要根据你的应用来设置。太小精度高但计算量大太大精度低但性能好误差敏感度errorSensitivity要根据画面质量要求来调整。值越小画质越好值越大性能越好重投影矩阵如果相机在移动需要传入正确的重投影矩阵否则着色率纹理会和画面对不上总结自适应 VRS 是一个很实用的图形优化技术它能在不明显影响画质的情况下显著降低 GPU 的渲染负载。核心流程查询设备是否支持自适应 VRS创建自适应 VRS 实例配置参数每帧调用HMS_XEG_CmdDispatchAdaptiveVRS生成着色率纹理GPU 根据着色率纹理渲染下一帧不需要时销毁实例掌握了这些你就能在 HarmonyOS 应用中实现自适应 VRS让你的 3D 应用跑得更流畅。