鸿蒙开发-Vulkan下也能自动省GPU?自适应可变速率着色
Vulkan 版自适应 VRS:用命令缓冲实现更灵活的着色率优化
如果你的图形引擎用的是 Vulkan 而不是 OpenGL ES,那自适应 VRS(Variable Rate Shading)的接入方式会有些不同。Vulkan 版的接口设计更符合 Vulkan 的风格——用句柄管理对象、用命令缓冲记录操作、用结构体传递参数。
这篇文章就来讲讲怎么在 Vulkan 环境下使用 XEngine 的自适应 VRS 特性。
Vulkan 版和 GLES 版有什么不同
最大的不同是 API 风格。GLES 版是直接调用函数,Vulkan 版是"创建对象 -> 录制命令 -> 销毁对象"的三步走模式。这和 Vulkan 本身的设计哲学是一致的:把操作记录到命令缓冲里,然后统一提交执行。
下面的流程图展示了 Vulkan 版自适应 VRS 的整体使用流程:
另外,Vulkan 版需要你通过HMS_XEG_EnumerateDeviceExtensionProperties接口来查询扩展支持,而不是 GLES 版的HMS_XEG_GetString。
第一步:查询扩展支持
在 Vulkan 里查询 XEngine 扩展:
// 先查询支持的扩展数量uint32_tpropertyCount=0;HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,&propertyCount,NULL);// 分配空间并查询扩展列表XEG_ExtensionProperties*properties=(XEG_ExtensionProperties*)malloc(sizeof(XEG_ExtensionProperties)*propertyCount);HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,&propertyCount,properties);// 检查是否支持自适应 VRSbool supportsAdaptiveVRS=false;for(uint32_ti=0;i<propertyCount;i++){if(strcmp(properties[i].extensionName,XEG_ADAPTIVE_VRS_EXTENSION_NAME)==0){supportsAdaptiveVRS=true;break;}}free(properties);if(!supportsAdaptiveVRS){return;// 不支持}HMS_XEG_EnumerateDeviceExtensionProperties接受三个参数:
physicalDevice:当前使用的 Vulkan 物理设备pPropertyCount:指向扩展数量的指针。当pProperties为 NULL 时,返回支持的扩展数量pProperties:指向XEG_ExtensionProperties数组的指针,用于接收查询结果
查询成功返回VK_SUCCESS,如果传入的数量不够则返回VK_INCOMPLETE。
XEG_ADAPTIVE_VRS_EXTENSION_NAME的值是"XEG_adaptive_vrs"。
第二步:创建 XEG_AdaptiveVRS 对象
Vulkan 的哲学是"万物皆对象"。自适应 VRS 也不例外,你需要先创建一个XEG_AdaptiveVRS句柄:
XEG_AdaptiveVRS adaptiveVRS=VK_NULL_HANDLE;XEG_AdaptiveVRSCreateInfo createInfo={.sType=XEG_STRUCTURE_TYPE_RT_SHADOWAO_CREATE_INFO,// 结构体类型// ... 其他创建参数};VkResult result=HMS_XEG_CreateAdaptiveVRS(device,&createInfo,&adaptiveVRS);if(result!=VK_SUCCESS){// 创建失败,处理错误return;}HMS_XEG_CreateAdaptiveVRS有三个参数:
device:当前使用的VkDevicepXegAdaptiveVRSCreateInfo:创建参数结构体的指针,不允许为空pXegAdaptiveVRS:指向句柄的指针,创建成功后句柄会写入这里
当创建参数(比如分辨率、渲染区域等)发生变化时,你需要销毁旧对象、创建新对象。
第三步:录制自适应 VRS 命令
有了句柄之后,你可以在 Vulkan 命令缓冲里录制自适应 VRS 的计算命令:
// 准备描述信息XEG_AdaptiveVRSDescription description={// ... 每一帧需要更新的参数};// 录制命令到命令缓冲HMS_XEG_CmdDispatchAdaptiveVRS(commandBuffer,adaptiveVRS,&description);HMS_XEG_CmdDispatchAdaptiveVRS有三个参数:
commandBuffer:当前记录命令的VkCommandBuffer,这个命令缓冲必须被提交到vkQueueSubmit才会真正执行xegAdaptiveVRS:已创建的XEG_AdaptiveVRS对象pXegAdaptiveVRSDescription:参数结构体的指针,不允许为空。这个结构体里的信息每一帧都需要更新
注意,这个函数只是把命令"录制"到命令缓冲里,并不会立即执行。你需要在合适的时候提交命令缓冲,命令才会真正运行。
第四步:销毁对象
当你不再需要自适应 VRS 的时候(比如游戏退出或者分辨率切换),记得销毁对象:
HMS_XEG_DestroyAdaptiveVRS(adaptiveVRS);这一步很重要。Vulkan 不像 OpenGL 那样有自动的资源回收机制,你创建的每一个对象都需要手动销毁。
完整流程
// 1. 查询扩展支持uint32_tpropertyCount=0;HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,&propertyCount,NULL);XEG_ExtensionProperties*properties=malloc(sizeof(XEG_ExtensionProperties)*propertyCount);HMS_XEG_EnumerateDeviceExtensionProperties(physicalDevice,&propertyCount,properties);bool supported=false;for(uint32_ti=0;i<propertyCount;i++){if(strcmp(properties[i].extensionName,XEG_ADAPTIVE_VRS_EXTENSION_NAME)==0){supported=true;break;}}free(properties);if(!supported)return;// 2. 创建对象XEG_AdaptiveVRS adaptiveVRS=VK_NULL_HANDLE;XEG_AdaptiveVRSCreateInfo createInfo={/* ... */};HMS_XEG_CreateAdaptiveVRS(device,&createInfo,&adaptiveVRS);// 3. 每帧:录制命令XEG_AdaptiveVRSDescription desc={/* 当前帧参数 */};HMS_XEG_CmdDispatchAdaptiveVRS(commandBuffer,adaptiveVRS,&desc);// 4. 退出时销毁HMS_XEG_DestroyAdaptiveVRS(adaptiveVRS);和 GLES 版的对比
下面的流程图对比了 GLES 版和 Vulkan 版在执行方式上的差异:
| 方面 | GLES 版 | Vulkan 版 |
|---|---|---|
| 查询接口 | HMS_XEG_GetString | HMS_XEG_EnumerateDeviceExtensionProperties |
| 参数设置 | HMS_XEG_AdaptiveVRSParameter逐个设置 | 通过结构体传递 |
| 执行方式 | 直接调用 | 录制到命令缓冲 |
| 资源管理 | 无显式销毁 | 需要手动创建和销毁 |
| 对象模型 | 无 | XEG_AdaptiveVRS句柄 |
Vulkan 版的代码量确实比 GLES 版多一些,但换来的是更灵活的命令管理和更好的多线程支持。
使用建议
对象生命周期管理:创建参数变化时需要重建对象。建议用一个标志位来跟踪参数是否变化。
命令缓冲要提交:
HMS_XEG_CmdDispatchAdaptiveVRS只是录制命令,记得在合适的时候提交命令缓冲。描述信息每帧更新:
XEG_AdaptiveVRSDescription里的信息每一帧都需要更新,因为着色率图是根据当前帧的输入动态计算的。错误检查:创建函数返回
VkResult,记得检查返回值是否为VK_SUCCESS。
Vulkan 版自适应 VRS 适合那些已经用 Vulkan 做渲染引擎的项目。虽然接入成本比 GLES 版高一些,但能更好地融入 Vulkan 的渲染管线。
