
HarmonyOS Vulkan 进阶用 Subpass Shading 降低带宽消耗什么是 Subpass Shading在图形渲染中带宽DDR Bandwidth是一个非常宝贵的资源。GPU 需要不断地从内存中读取和写入数据如果带宽不够就会成为性能瓶颈。传统的渲染流程中每个 Subpass子通道都会把自己的渲染结果写入内存然后下一个 Subpass 再从内存中读取。这个写入-读取的过程就消耗了大量的带宽。Subpass Shading 是一种优化技术它允许在同一个 Render Pass 内让不同的 Subpass 之间直接共享数据而不需要经过内存。这就像两个同事之间直接传文件而不是先把文件存到硬盘另一个人再从硬盘读取。环境搭建硬件要求设备类型请参考 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 // 渲染 │ ├── ets │ │ ├── entryability │ │ │ └── EntryAbility.ts // 程序入口类 │ │ ├── pages │ │ │ └── index.ets // 主界面展示类 │ ├── resources // 资源文件目录 │ │ ├── base │ │ │ ├── media │ │ │ └── icon.png // 图片资源 │ │ ├── rawfile │ │ │ ├── forward_plus // forward plus shader文件 │ │ │ ├── sponza_full // 模型文件 │ │ │ ├── subpass_shading // subpass shading shader文件这个项目使用 Vulkan 图形 API所以代码主要在 C 层。第一步引入头文件#includevulkan/vulkan_core.hVulkan 的头文件已经包含在 HarmonyOS SDK 中不需要额外下载。第二步配置 CMakeLists.txtfind_library( hilog-lib hilog_ndk.z ) find_library( libace-lib ace_ndk.z ) find_library( libnapi-lib ace_napi.z ) find_library( libuv-lib uv ) find_library( libvulkan-lib vulkan ) find_library( libvsync-lib native_vsync ) target_link_libraries(nativerender PUBLIC ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} ${libvulkan-lib} libnative_window.so libc.a librawfile.z.so ${libvsync-lib})这里链接了 Vulkan 库libvulkan-lib这是我们使用 Vulkan API 的基础。第三步编写 Subpass Shading Shader首先需要编写支持 Subpass Shading 的 Shader// Shader中需要使能两个扩展声明 #extension GL_HUAWEI_subpass_shading : require #extension GL_KHR_shader_subgroup_arithmetic : require layout(set0, binding0, input_attachment_index0) uniform subpassInput depth; void main() { float depth subpassLoad(depthAttachment).x; float minDepth subgroupMin(depth); float maxDepth subgroupMax(depth); }关键点GL_HUAWEI_subpass_shading华为的 Subpass Shading 扩展GL_KHR_shader_subgroup_arithmeticSubgroup 算术操作扩展subpassInput声明一个输入附件可以从上一个 Subpass 读取数据subpassLoad从输入附件中读取数据subgroupMin/subgroupMax在 Subgroup 内计算最小值和最大值第四步分发执行 Subpass Shading在 Vulkan 命令缓冲区中分发 Subpass Shading// commandBuffer为命令缓冲区用户可自定义VkCommandBuffer commandBuffer;// subpassShadingPipeline为子通道着色管线用户可自定义VkPipeline subpassShadingPipeline;// subpassShadingPipelineLayout为子通道着色管线布局用户可自定义VkPipelineLayout subpassShadingPipelineLayout;// 第一个被绑定的descriptor set的set编号uint32_tfirstSet;// pDescriptorSets数组中的元素的数量uint32_tdescriptorSetCount;// pDescriptorSets为描述符集用户可自定义VkDescriptorSet*pDescriptorSets;// pDynamicOffset数组中的动态偏移的数量uint32_tdynamicOffsetCount;// 指向一个uint32_t数组表示动态偏移的量uint32_t*pDynamicOffsets;// 切换到下一个子通道vkCmdNextSubpass(commandBuffer,VK_SUBPASS_CONTENTS_INLINE);// 使用子通道着色管线的bindpointvkCmdBindPipeline(commandBuffer,VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI,subpassShadingPipeline);// 绑定描述符集vkCmdBindDescriptorSets(commandBuffer,VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI,subpassShadingPipelineLayout,firstSet,descriptorSetCount,pDescriptorSets,dynamicOffsetCount,pDynamicOffsets);// device为逻辑设备用户可自定义VkDevice device;// 分发子通道着色PFN_vkCmdSubpassShadingHUAWEI vkCmdSubpassShadingHUAWEI(PFN_vkCmdSubpassShadingHUAWEI)vkGetDeviceProcAddr(device,vkCmdSubpassShadingHUAWEI);vkCmdSubpassShadingHUAWEI(commandBuffer);// 结束渲染通道vkCmdEndRenderPass(commandBuffer);关键 API 解释vkCmdNextSubpass切换到下一个 SubpassVK_SUBPASS_CONTENTS_INLINE表示命令直接记录在主命令缓冲区中vkCmdBindPipeline绑定管线VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI是华为扩展的 Bind Point专门用于 Subpass ShadingvkCmdSubpassShadingHUAWEI这是华为扩展的函数需要通过vkGetDeviceProcAddr动态获取执行 Subpass Shading 计算第五步创建 Subpass Shading Render Pass这是最复杂的部分需要创建一个包含两个 Subpass 的 Render Pass// attachmentDescrs为附件描述VkAttachmentDescription2 attachmentDescrs[1];// inputAttachmentRefs为输入附件引用VkAttachmentReference2 inputAttachmentRefs[1];// depthAttachmentRef为深度附件引用VkAttachmentReference2 depthAttachmentRef[1];// subpass为子通道描述VkSubpassDescription2 subpass;subpass.sTypeVK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2;subpass.pipelineBindPointVK_PIPELINE_BIND_POINT_GRAPHICS;subpass.pDepthStencilAttachmentdepthAttachmentRef[0];// subpass1为子通道1描述VkSubpassDescription2 subpass1{};subpass1.sTypeVK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2;// 使用subpass shading的bindpointsubpass1.pipelineBindPointVK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI;subpass1.inputAttachmentCount1;subpass1.pInputAttachmentsinputAttachmentRefs[0];// subpasses为子通道描述数组VkSubpassDescription2 subpasses[2]{subpass,subpass1};// fragmentToCompute为内存屏障VkMemoryBarrier2KHR fragmentToCompute{};fragmentToCompute.sTypeVK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR;fragmentToCompute.pNextnullptr;fragmentToCompute.srcStageMaskVK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR;// 使用subpass shading的标志位fragmentToCompute.dstStageMaskVK_PIPELINE_STAGE_2_SUBPASS_SHADING_BIT_HUAWEI;fragmentToCompute.srcAccessMaskVK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;fragmentToCompute.dstAccessMaskVK_ACCESS_INPUT_ATTACHMENT_READ_BIT;// dependency为子通道依赖VkSubpassDependency2 dependency[1]{};dependency[0].sTypeVK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2;dependency[0].pNextfragmentToCompute;dependency[0].srcSubpass0;dependency[0].dstSubpass1;// 创建render pass create infoVkRenderPassCreateInfo2 rpInfo{};rpInfo.sTypeVK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;rpInfo.attachmentCount1;rpInfo.pAttachmentsattachmentDescrs;rpInfo.subpassCount2;rpInfo.pSubpassessubpasses;rpInfo.dependencyCount1;rpInfo.pDependenciesdependency;// device为逻辑设备用户可自定义VkDevice device;// renderPass为渲染通道用户可自定义VkRenderPass renderPass;// 创建渲染通道vkCreateRenderPass2(device,rpInfo,nullptr,renderPass);这段代码的核心逻辑两个 Subpass第一个 Subpass使用VK_PIPELINE_BIND_POINT_GRAPHICS执行正常的图形渲染写入深度附件第二个 Subpass使用VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI执行 Subpass Shading读取第一个 Subpass 的深度数据内存屏障srcStageMask VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR源阶段是片段着色器dstStageMask VK_PIPELINE_STAGE_2_SUBPASS_SHADING_BIT_HUAWEI目标阶段是 Subpass Shading这确保了第一个 Subpass 写入深度数据后第二个 Subpass 才能读取子通道依赖srcSubpass 0依赖第一个 SubpassdstSubpass 1被第二个 Subpass 依赖第六步创建 Subpass Shading Pipeline// device为逻辑设备用户可自定义VkDevice device;// subpassShadingPipeline为子通道着色管线用户可自定义VkPipeline subpassShadingPipeline;// renderPass为渲染通道用户可自定义VkRenderPass renderPass;// shaderModule为着色器模块用户可自定义VkShaderModule shaderModule;// subpassShadingConstantMapEntries定义Host中数据和着色器数据的映射关系VkSpecializationMapEntry subpassShadingConstantMapEntries[]{{0,0*sizeof(uint32_t),sizeof(uint32_t)},{1,1*sizeof(uint32_t),sizeof(uint32_t)}};// maxWorkgroupSize为子通道着色最大工作组大小VkExtent2D maxWorkgroupSize;// 获取subpass shading最大工作组大小PFN_vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI(PFN_vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI)vkGetDeviceProcAddr(device,vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI);vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI(device,renderPass,maxWorkgroupSize);// subpassShadingConstants定义特化常量信息用户可自定义VkSpecializationInfo subpassShadingConstants{2,subpassShadingConstantMapEntries,sizeof(VkExtent2D),maxWorkgroupSize};// subpassShadingPipelineCreateInfo为子通道着色管线创建所需信息VkSubpassShadingPipelineCreateInfoHUAWEI subpassShadingPipelineCreateInfo{VK_STRUCTURE_TYPE_SUBPASS_SHADING_PIPELINE_CREATE_INFO_HUAWEI,NULL,renderPass,1};VkPipelineShaderStageCreateInfo subpassShadingPipelineStageCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,NULL,0,VK_SHADER_STAGE_SUBPASS_SHADING_BIT_HUAWEI,shaderModule,main,subpassShadingConstants};// subpassShadingPipelineLayout为用户自定义创建的pipelinelayoutVkPipelineLayout subpassShadingPipelineLayout;VkPipeline basePipelineHandle;int32_tbasePipelineIndex;// computePipelineCreateInfo为子通道着色计算管线创建所需信息VkComputePipelineCreateInfo subpassShadingComputePipelineCreateInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,subpassShadingPipelineCreateInfo,0,subpassShadingPipelineStageCreateInfo,subpassShadingPipelineLayout,basePipelineHandle,basePipelineIndex};// pipelineCache为管线缓存VkPipelineCache pipelineCache;// 创建子通道着色计算管线vkCreateComputePipelines(device,pipelineCache,1,subpassShadingComputePipelineCreateInfo,NULL,subpassShadingPipeline);关键点获取最大工作组大小vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI是华为扩展的函数它返回设备支持的最大工作组大小用于配置 Shader特化常量VkSpecializationInfo用于向 Shader 传递常量数据这里传递的是最大工作组大小管线创建使用VkComputePipelineCreateInfo创建管线但实际上它是 Subpass Shading 管线不是普通的计算管线Subpass Shading 的完整实现流程如下编写 Subpass Shading Shader获取最大工作组大小创建 Render Pass 包含两个 Subpass设置 Subpass 间内存屏障和依赖创建 Subpass Shading Pipeline开始 Render PassSubpass 0: 图形渲染写入深度切换到 Subpass 1绑定 Subpass Shading Pipeline分发 Subpass Shading结束 Render Pass传统渲染与 Subpass Shading 的数据流对比SubpassShading方式Subpass 0 渲染Tile 内存直接共享Subpass Shading 处理传统方式Subpass 0 渲染写入内存从内存读取Subpass 1 处理Subpass Shading 的优势降低带宽消耗Subpass 之间直接共享数据不需要经过内存减少延迟数据不需要写入内存再读取减少了等待时间提高性能特别是在需要多个 Pass 处理的场景中适用场景Subpass Shading 特别适合以下场景延迟渲染需要多个 Pass 处理光照、阴影等后处理效果如模糊、景深、HDR 等需要读取前一个 Pass 结果的场景注意事项设备支持不是所有设备都支持 Subpass Shading需要检查设备扩展Shader 编写需要使用华为的扩展语法Render Pass 设计要正确设置 Subpass 之间的依赖关系内存屏障要确保数据的可见性总结Subpass Shading 是一个强大的图形优化技术它可以显著降低渲染过程中的带宽消耗。核心流程编写支持 Subpass Shading 的 Shader创建包含多个 Subpass 的 Render Pass设置正确的内存屏障和子通道依赖创建 Subpass Shading Pipeline在命令缓冲区中分发执行虽然代码看起来比较复杂但核心思想很简单让相邻的 Subpass 直接共享数据避免不必要的内存读写。这对于提升图形渲染性能是非常有帮助的。