三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

vulkan_best_practice_for_mobile_developers常见问题解答:解决移动端Vulkan开发难题

vulkan_best_practice_for_mobile_developers常见问题解答:解决移动端Vulkan开发难题

vulkan_best_practice_for_mobile_developers常见问题解答:解决移动端Vulkan开发难题

【免费下载链接】vulkan_best_practice_for_mobile_developersVulkan best practice for mobile developers项目地址: https://gitcode.com/gh_mirrors/vu/vulkan_best_practice_for_mobile_developers

vulkan_best_practice_for_mobile_developers是面向移动开发者的Vulkan最佳实践项目,旨在帮助开发者解决移动端Vulkan开发过程中遇到的各种难题,提升应用性能与稳定性。

一、渲染同步与布局转换问题

1.1 交换链图像获取时的布局转换

Q:如何将交换链图像转换为所需布局?隐式转换(initialLayout → layout)是否足够?

A:默认转换可能无法满足高效获取需求。VkSubmitInfo允许传递带有pWaitDstStageMask的获取信号量,最佳值通常为VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,因为只需在实际写入时确保交换链图像就绪。

问题在于图像的隐式转换不会等待该阶段。若存在不匹配,GPU可能在图像完全获取前尝试转换,导致结果未定义。解决方法有两种:

  • 放弃优化获取,将pWaitDstStageMask设为TOP_OF_PIPE(不推荐);
  • 用显式子通道依赖替换隐式依赖,考虑正确的阶段掩码。

以下是示例子通道依赖:

VkSubpassDependency dependency = { 0 }; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = 0; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

另一种更简单的方法是禁用隐式依赖(设置initialLayout = layout),通过管线屏障处理布局转换,确保在图像获取后正确阶段进行转换。

1.2 通过initialLayout/finalLayout进行隐式图像转换

Q:使用initialLayout → layout → finalLayout在渲染通道间转换图像时出现渲染 artifacts,原因是什么?

A:这源于未正确同步渲染通道。Vulkan需要显式同步,即使看似可推断。GPU可能无序执行渲染通道,除非明确标记依赖关系。

问题在于未指定转换的完成时间。解决方案是放弃隐式转换,设置initialLayout = layout = finalLayout,通过管线屏障处理转换,在命令缓冲区创建时声明,比子通道依赖更灵活。

对于交换链图像,设置initialLayout = UNDEFINEDfinalLayout = PRESENT_SRC_KHR时,只要将队列提交的signalSemaphore传递给vkQueuePresentKHR,最终转换到PRESENT_SRC_KHR是安全的,但初始转换需参考上述交换链图像获取时的布局转换方法。

二、设备丢失(DEVICE_LOST)问题

Q:调用vkQueueSubmitvkWaitForFences时出现DEVICE_LOST错误,验证层无提示,可能原因是什么?

AVK_ERROR_DEVICE_LOST通常由两种主要原因引起:内存不足(OOM)和资源损坏。

若应用在移动设备正常使用下顶点数量在合理范围(约200万左右),则需排查因同步缺失导致的资源损坏。同步问题的常见迹象包括闪烁和设备间不一致。应用API使用不正确可能在某些平台运行正常,在其他平台失败。

验证层虽不能覆盖所有情况,但有助于排查。建立渲染管线数据依赖的心理模型至关重要。调试同步问题的一种方法是临时添加更多同步(如额外的管线屏障、等待空闲),以缩小同步缺失点。

Vulkan调试图形:展示了Vulkan渲染流程中的各个组件和它们之间的关系,有助于理解同步问题产生的位置。

三、命令缓冲区与多线程渲染问题

3.1 多线程渲染性能

Q:设置多线程渲染后运行比单线程慢,可能原因是什么?

A:多线程命令提交有可能显著提升CPU时间,但也存在一些陷阱,最坏情况下性能比单线程更差。常见问题包括:

  • 线程生成开销大:直接使用std::async生成线程可能导致显著开销,STL实现通常不为此池化线程。建议使用线程池库或自行实现线程池。
  • 同步开销显著:使用互斥锁保护所有映射访问可能导致代码以序列化方式运行,并增加锁获取/释放的额外开销。可使用std::shared_mutex等读写互斥锁,或通过确保多线程代码执行时映射为只读实现无锁。
  • 每个线程的网格数量少:多线程命令记录在CPU(线程成本)和GPU(执行次级命令缓冲区)方面都有性能开销,因此并非始终适合使用全部可用并行性。经验法则是,仅在测量到绘制调用记录占用帧时间的显著部分时才采用并行。

多线程命令缓冲区使用:展示了在Mali-G76 GPU上,使用8线程多线程渲染时的帧时间和CPU周期等性能指标。

四、描述符管理问题

Q:应该有多少个描述符池?是一个大的还是每个帧一个?

A:每个帧使用一个描述符池并非绝对必要,但非常推荐。如果创建描述符池时不使用FREE_DESCRIPTOR_SET_BIT标志,则只能通过vkResetDescriptorPool释放池。

如果对所有帧使用单个池,释放前必须等待空闲。若使用多个描述符池,则可以释放当前未使用的帧的池。避免FREE_DESCRIPTOR_SET_BIT标志可让驱动程序使用更简单的分配器,最终提高性能。

更多信息可查看描述符管理教程。如果进行多线程渲染,可能需要分配更多描述符池,如多线程教程中所述。

描述符管理示例:在Mali-G76 GPU上,启用描述符集缓存和禁用单一大VkBuffer时的帧时间表现。

五、管线屏障与同步问题

5.1 理解屏障范围

Q:假设只使用一个队列,有如下代码,两个屏障如何与各命令集交互?

// Set of commands - A vkCmdDraw(...) ... vkCmdDraw(...) // Barrier 1 vkCmdPipelineBarrier(...) // Set of commands - B vkCmdDraw(...) ... vkQueueSubmit(...) vkQueuePresentKHR(...) // Barrier 2 vkCmdPipelineBarrier(...) // Set of commands - C vkCmdDraw(...) ... vkQueueSubmit(...) vkQueuePresentKHR(...)

A:管线屏障始终作用于两组命令:屏障之前的命令和之后的命令。

若在渲染通道实例外调用vkCmdPipelineBarrier,则第一组命令是所有先前提交到队列并记录在命令缓冲区中的命令,第二组命令是所有后续记录在命令缓冲区并提交到队列的命令。

两个屏障的主要区别在于,第一个在命令缓冲区中间,第二个在第一个命令提交和呈现之后(可能在另一个命令缓冲区中)。根据规范,这种差异并不重要,因为先前提交和先前记录在当前命令缓冲区中的命令处理方式相同。

两个屏障的分解:

  • Barrier1
    • 之前:集合A和之前的所有内容
    • 之后:集合B、C和之后的所有内容
  • Barrier2
    • 之前:集合A、B和之前的所有内容
    • 之后:集合C和之后的所有内容

Mali三阶段流水线:展示了CPU、几何阶段和片段阶段之间的队列提交和执行关系,有助于理解屏障在流水线中的作用。

六、内存管理问题

6.1 为缓冲区分配和映射内存

Q:分配和映射缓冲区内存的最佳实践是什么?

A:通过vkAllocateMemory为每个缓冲区分配内存可能非常慢,并且分配总数有限制,此外通过vkMapMemory映射内存是一项成本高昂的操作。应用的预期用法是分配一大块内存,保持映射并进行管理。

如果需要遵循这些最佳实践的内存管理即插即用替代品,可查看VMA。其API与Vulkan类似,可能不需要对代码进行重大更改。

6.2 Android上无回溯崩溃

Q:Android应用在logcat上没有任何消息或回溯就崩溃,可能是什么原因?

A:应用可能内存不足。在logcat中查找类似以下消息:

07-13 17:10:37.788 19132 19132 V threaded_app: LowMemory: 0x7926307ec0

如果内存不足,在Android Studio Profiler中调试应用可能会有所帮助,因为它可以跟踪应用的内存使用情况,并可能将其追溯到单个分配。

七、着色器相关问题

7.1 着色器变体

Q:如何在Vulkan中设置着色器变体?应该使用特化常量吗?

A:着色器变体的第一种方法是在着色器中使用#ifdef指令,然后通过glslangValidator-D选项编译不同变体,可在编译时或运行时进行。

另一种方法是使用特化常量,它们效率高,仍是编译时常量,在管线创建时指定,无需使用glslangValidatorshaderc编译单独的变体。但特化常量有一些限制,主要是在定义着色器接口时不能使用if语句,如顶点属性、纹理采样器等。因此,着色器接口是固定的,但可以在main()函数中基于特化常量使用if语句,就像#define一样在编译时求值。即使不能修改着色器接口变量,编译器也可能优化掉不需要的变量(如果删除所有对它们的引用)。

7.2 统一缓冲区和推送常量的结构体对齐

Q:如何将数据从C/C++结构体传递到统一缓冲区?传递的数据在着色器中无法正确读取。

A:由于结构打包规则,统一缓冲区对齐并不简单:C++中的结构体除非仔细构造,否则不会与GLSL中的结构体匹配。可查看std140打包规则,该规则同时适用于统一缓冲区和推送常量。调试可能很困难,幸运的话验证层会抱怨一些意外的偏移,否则只会看到传递给着色器的奇怪值。

黄金法则是结构体和数组成员必须对齐为16字节(vec4的大小)的倍数。因此:

  • vec4mat4是安全的,可放心使用;
  • 不要使用vec3,使用vec4并在可能的情况下在第4个分量中打包其他信息;
  • 如果需要使用float/int32_t,需要在它们之后添加vec3的填充;尽可能将基本类型打包到vec4中。

动态统一缓冲区对动态偏移有额外的对齐要求,因此可能需要进一步填充统一缓冲区数据,使偏移是该限制的精确倍数。可在VkPhysicalDeviceProperties中检查minUniformBufferOffsetAlignment限制,常见值在16到256字节之间。

通过以上常见问题的解答,希望能帮助移动端Vulkan开发者更好地解决开发过程中遇到的难题,充分利用vulkan_best_practice_for_mobile_developers项目提升应用性能。

【免费下载链接】vulkan_best_practice_for_mobile_developersVulkan best practice for mobile developers项目地址: https://gitcode.com/gh_mirrors/vu/vulkan_best_practice_for_mobile_developers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

← 返回列表