为 Vulkan 提供显式管道缓存控制

Vulkan® 工作组发布了VK_KHR_pipeline_binary扩展,可以直接检索与各个管道相关的二进制数据,绕过 VkPipelineCache 机制,并允许应用程序明确管理管道缓存。

VkPipelineCache 对象旨在使 Vulkan 驱动程序能够在不同的管道之间重用状态块或着色器代码。最初的想法是驱动程序最清楚哪些部分的状态可以重用,而应用程序只需要管理存储和线程,从而简化开发人员的代码。

然而,随着时间的推移,VkPipelineCache 对象被证明过于不透明,这促使 Vulkan 工作组发布了许多扩展,以便为应用程序提供更多的控制权。VkPipelineCache 对象的当前功能可以满足许多应用程序的需求,但在更高级的用例中存在缺陷。

特别是,VkPipelineCache 对象在以下场景中可能难以使用:

  • 缓存大小和修剪受限:VkPipelineCache API 无法控制其所包含的二进制对象的生命周期。例如,想要实现 LRU 缓存的应用程序很难使用 VkPipelineCache 对象。
  • 与应用程序缓存集成:某些应用程序维护 VkPipeline 对象的缓存。VkPipelineCache API 使得无法有效地将 VkPipelineCache 对象内的缓存二进制对象与应用程序自己的缓存条目关联起来。

此外,大多数驱动程序都维护着管道派生的二进制对象的内部缓存。在某些情况下,应用程序直接与该内部缓存交互会很有帮助,尤其是在一些专用平台上,如下所述。

在考虑如何满足这些高级需求时,Vulkan 工作组认为,通过添加更多旋钮来使现有的 VkPipelineCache API 变得更加复杂并不是一个可持续的解决方案。相反,新的 VK_KHR_pipeline_binary 扩展引入了一种干净的新方法,它为应用程序提供了对二进制 blob 和最佳缓存所需信息的访问,同时与应用程序自己的缓存机制顺利集成。

值得注意的是,VK_EXT_shader_object 扩展已包含与 VK_KHR_pipeline_binary 类似的功能。这两个扩展同时开发,以提供通用解决方案,包括尚不支持 VK_EXT_shader_object 扩展的设备。

不需要新 VK_KHR_pipeline_binary 扩展的高级功能的应用程序可以继续使用 VkPipelineCache 对象,以实现简单性和优化的实现。但对 VkPipelineCache API 不满意的开发人员应该继续阅读以了解有关这种强大的新方法的更多信息。

使用 VK_KHR_pipeline_binary 进行缓存

要了解 VK_KHR_pipeline_binary,我们首先看看驱动程序如何处理缓存。当然,有 VkPipelineCache 对象,但一些 Vulkan 实现(无论是驱动程序还是层)还维护从管道生成的 blob 的内部缓存!

此内部缓存可提高无法有效使用 VkPipelineCache 对象的应用程序的性能,允许驱动程序在看似不相关的管道之间共享 blob(即使在创建它们时未提供相同的 VkPipelineCache 对象),并允许在某些专用平台上预填充缓存。

无论如何,缓存都包含两个部分:生成 blob 并缓存它们!让我们首先研究一下 blob 的生成。

当驱动程序创建管道时,它可能会生成多个二进制 blob。一些是阶段着色器代码和管道静态状态的函数,一些还考虑了周围着色器阶段的接口,而另一些则纯粹是管道静态状态的函数。例如,在仅具有顶点和片段着色器的管道中,具有静态顶点输入和混合状态,可能会派生以下 blob。

为 Vulkan 提供显式管道缓存控制

请注意,在创建完整的管道时,其中一些 blob 可能会合并(例如,通过使用状态 blob 来修补着色器 blob),但这与本次讨论无关。

例如,许多管道可能使用相同的顶点着色器,因此上例中的 Blob2 很容易在多个管道之间共享。许多管道很可能共享相同的顶点输入和片段输出状态,因此 Blob1 和 Blob4 更有可能在多个管道之间共享。以上就是 VK_EXT_graphics_pipeline_library和VK_EXT_shader_object扩展能够正常工作的原因。

现在生成 blob 是一回事,但为了使缓存有效,驱动程序必须能够在生成 blob 之前在缓存中查找它们!为此,需要为每个 blob 生成一个密钥。如上所示,每个 blob 的密钥取决于管道 CreateInfo 结构的不同位。

因此,此扩展最重要的方面实际上是以最佳方式检索正确的密钥。

要获取每个 blob 的密钥和数据,应使用 VK_PIPELINE_CREATE_2_CAPTURE_DATA_BIT_KHR 标志创建管道。然后,可以使用 vkCreatePipelineBinariesKHR() 从管道中创建 VkPipelineBinaryKHR 对象(封装 blob),并使用 vkGetPipelineBinaryDataKHR() 检索其密钥和内容。在这种情况下,创建 VkPipelineBinaryKHR 对象时使用 VkPipelineBinaryCreateInfoKHR::pipeline。

为 Vulkan 提供显式管道缓存控制

然后,应用程序可以按照自己喜欢的任何方式存储这些密钥/数据对。某些应用程序可能已经创建了以管道 CreateInfo(或 CreateInfo 所源自的信息)为密钥的缓存,因此它们可以将检索到的 blob 与相同的信息相关联。在这种情况下,需要注意的是,如果不同的管道生成相同的 blob,则这些 blob 可能与相同的密钥相关联,应用程序应相应地对 blob 进行重复数据删除。

VK_KHR_pipeline_binary 扩展为尚未拥有基于管道 CreateInfo 的缓存的应用程序提供了一个便利函数。vkGetPipelineKeyKHR() 函数将根据其 CreateInfo 为管道生成一个密钥,因此 blob 可以与该密钥关联。这完全是可选的,但请记住,驱动程序生成的密钥将跳过不影响二进制 blob 的状态,因此可能比应用程序生成的密钥更好。

在应用程序的下一次运行中,当缓存已准备好并且从持久存储中检索到 blob 时,应用程序可以尝试仅从其 blob 创建管道。这可以通过将 VkPipelineBinaryInfoKHR 链接到管道 CreateInfo 来实现,在这种情况下,可以从 CreateInfo 中省略着色器模块(这意味着应用程序根本不需要加载 SPIR-V)。在这种情况下,在创建 VkPipelineBinaryKHR 对象时使用 VkPipelineBinaryCreateInfoKHR::pKeysAndDataInfo。

为 Vulkan 提供显式管道缓存控制

就是这样,应用程序可以随意管理这些 blob。上面的内容摘要如下图所示。

创建管道并检索二进制文件:

创建管道并检索二进制文件

从缓存中的二进制文件创建管道:

为 Vulkan 提供显式管道缓存控制

Blob 的有效性

从管道中检索到的二进制 blob 不会永远存在。例如,驱动程序更新可能会导致 blob 内容发生变化,或者在某些情况下会显著改变管道拆分为 blob 的方式。此外,blob 内容可能会根据全局启用的 Vulkan 功能(例如稳健性或受保护的内存)而有所不同。

要确定二进制 blob 对设备是否有效,可以检索全局管道二进制密钥。只要该全局密钥与检索管道二进制文件的设备的全局密钥匹配,则该管道二进制文件有效,并可用于创建管道。

只需调用 vkGetPipelineKeyKHR() 即可检索全局密钥,无需提供管道 CreateInfo。

驱动程序的内部缓存

借助 VK_KHR_pipeline_binary 扩展,应用程序能够高效地重现驱动程序的内部缓存,同时将其与自己的缓存集成。但是,在某些情况下,实现中存在内部缓存仍然是首选。

通过此扩展公开内部缓存的一个好处是,应用程序能够提前异步创建二进制对象,有效地从磁盘提取缓存内容,并避免在管道创建过程中出现微卡顿,否则必须执行磁盘 I/O。此外,这允许应用程序提前知道哪些管道二进制文件缺失,稍后需要编译。

专用平台和内部缓存属性

专用平台可能会提供额外的功能,使内部缓存更具吸引力。例如,Steam为 Vulkan 游戏提供了一项很棒的功能,即管道缓存内容分布在客户端之间。这意味着即使是游戏的第一次运行也可以观察并受益于热缓存!在这种情况下,应用程序通过使用实现的内部缓存(即 Steam 提供的 Vulkan 层)节省了大量的管道创建时间。

为此,VK_KHR_pipeline_binary 扩展公开了许多属性:

  • pipelineBinaryInternalCache 声明存在内部缓存。应用程序实际上可以直接从此内部缓存中检索 blob,而无需先创建管道。在这种情况下,创建 VkPipelineBinaryCreateInfoKHR::pPipelineCreateInfo 时会使用 VkPipelineBinaryKHR 对象。如果所需的 blob 不在缓存中,此操作可能会失败。
  • pipelineBinaryInternalCacheControl 表示可以禁用内部缓存。对于应用程序和实现来说,维护相同的缓存都是浪费,因此如果此属性为 true,应用程序可以禁用驱动程序的内部缓存。这可以通过将 VkDevicePipelineBinaryInternalCacheControlKHR 链接到 VkCreateDeviceInfo 来实现。
  • pipelineBinaryPrefersInternalCache 指示 Vulkan 实现是否倾向于应用程序使用实现的内部缓存而不是其自己的缓存。
  • pipelineBinaryPrecompiledInternalCache 表示内部缓存可能包含应用程序从未生成过的二进制文件(如上文 Steam 功能所述)。在这种情况下,鼓励应用程序尝试从内部缓存中检索 blob。

在具有内部二进制缓存的专用平台上,应用程序应优先使用内部缓存,因为平台提供了pipelineBinaryPrefersInternalCache指示的附加功能。否则,如果应用程序正在维护缓存,则应尽可能禁用实现的内部缓存以避免重复工作。如果应用程序不维护缓存,则它可能选择使用实现的内部缓存。

结论

此扩展经过数年开发,多位 Vulkan 工作组成员付出了巨大努力,将其塑造成一种形式,使开发人员能够在不牺牲应用程序性能的情况下解决问题。我们很高兴以 VK_KHR_pipeline_binary 扩展的形式与您分享这项工作的成果,并好奇地想知道社区围绕它构建了什么以及应用程序如何从中受益。

作者:Shahbaz Youssefi
原文:https://www.khronos.org/blog/bringing-explicit-pipeline-caching-control-to-vulkan

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/51926.html

(0)

相关推荐

发表回复

登录后才能评论