VK_KHR_dynamic_rendering_local_read

此扩展允许从动态渲染通道中先前片段着色器写入的附件和资源中读取数据。

1. 问题陈述

VK_KHR_dynamic_rendering 为应用程序提供了一种更直接的方法来设置渲染代码,而无需预先准备大型专用对象。该扩展使许多不使用多个子通道的应用程序能够使用更精简的方法来启动渲染。

但是,使用多个子通道或想要执行诸如与顺序无关的透明度或简单的延迟渲染之类的应用程序无法使用 VK_KHR_dynamic_rendering,因为如果没有跨多个单独的渲染通道破坏渲染,则没有表达子通道依赖关系的路径。

为使用这些技术的应用程序添加一种在动态渲染中表达这些技术的方法,将使更多开发人员能够利用此功能,而无需设置渲染通道对象的复杂性。

2. 解决方案空间

解决此问题必须涉及某种允许将本地附件读取添加到动态渲染的方法,并且还存在以下附加约束

  • 该解决方案必须保持易于使用,以符合动态渲染的核心目标。

  • 该解决方案应尽量减少与使用渲染通道对象的多通道代码的偏差,以方便移植。

  • 该解决方案应可以在所有平台上高效实现,但允许供应商快速路径的空间。

3. 提案

3.1. 功能

以下功能通告此扩展的全部功能

typedef struct VkPhysicalDeviceDynamicRenderingLocalReadFeaturesKHR {
    VkStructureType                 sType;
    const void*                     pNext;
    VkBool32                        dynamicRenderingLocalRead;
} VkPhysicalDeviceDynamicRenderingLocalReadFeaturesKHR;

3.2. 动态渲染自依赖

如果启用了 dynamicRenderingLocalReads 功能,则如果流水线屏障包含 VK_DEPENDENCY_BY_REGION_BIT,并且源阶段和目标阶段均为帧缓冲区空间阶段,则现在允许在动态渲染中进行流水线屏障。当提供这样的流水线屏障时,如果任何重叠的片段位置(x,y,层/视图,样本)写入了指定的任何资源(如果使用内存屏障,则为全部),则同一渲染通道中的后续片段着色器可以读取该资源。这些流水线屏障不能执行布局转换或队列族传输。读取先前片段着色器写入的值之外的数据具有未定义的行为。

在写入存储资源时,资源中的实际位置并不重要,重要的是访问值的片段位置。例如,如果位置 (x=5,y=5) 的片段写入存储图像中的位置 (x=6,y=6) 和 (x=21,y=700),那么后续位置 (x=5,y=5) 的片段将能够从同一个存储图像中读取 (x=6,y=6) 和 (x=21,y=700),前提是在访问之间有适当的屏障。在同一个例子中,如果任何其他片段写入存储图像中的 (x=5,y=5),那么从 (x=5,y=5) 读取将构成数据竞争。这允许应用程序将任意数量的数据与给定像素关联,并扩展到使用缓冲区或设备地址。

用于此目的的图像必须采用 VK_IMAGE_LAYOUT_GENERAL 布局或新的专用布局。

VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR = 1000232000;

此布局可用于存储图像,以及渲染通道的颜色、深度/模板和输入附件。对附件的写入只能通过输入附件以这种方式可见,并且通过其他资源类型进行的写入不会通过输入附件可见。

虽然相同的布局可用于存储图像和所有附件,但仍然无法在同一渲染通道实例中通过一种类型的资源写入,然后通过另一种类型的资源读取。

3.3. 颜色附件重映射

为了方便应用程序将多通道渲染移植到动态渲染,添加了以下功能,允许在渲染期间重映射颜色附件的位置。

typedef struct VkRenderingAttachmentLocationInfoKHR {
    VkStructureType                 sType;
    const void*                     pNext;
    const uint32_t                  colorAttachmentCount;
    const uint32_t*                 pColorAttachmentLocations;
} VkRenderingAttachmentLocationInfoKHR;

void vkCmdSetRenderingAttachmentLocationsKHR(
    VkCommandBuffer                             commandBuffer,
    const VkRenderingAttachmentLocationInfoKHR* pLocationInfo);

与渲染通道对象一样,此信息必须在创建管线和渲染期间都提供,并且两者之间必须匹配才能有效。

此信息可以在管线创建期间通过将 VkRenderingAttachmentLocationInfoKHR 链接到 VkGraphicsPipelineCreateInfo,在需要片段输出状态子集时提供。如果在管线创建时未提供此结构,则等效于将 pColorAttachmentLocations 的每个元素的值设置为其在数组中的索引值,并且 colorAttachmentCount 等于 VkPipelineRenderingCreateInfoKHR::colorAttachmentCount 的值。

vkCmdSetRenderingAttachmentLocationsKHR 必须仅在动态渲染通道实例中调用。如果未调用此命令,则默认状态是 pColorAttachmentLocations 的每个元素都等于其在数组中的索引值。

pColorAttachmentLocations 的每个元素的索引对应于动态渲染通道中颜色附件的相同索引,并且该元素的值将成为引用它的位置,从而提供了一种重映射颜色附件位置的方法。这不允许应用程序完全交换颜色附件,但是如果应用程序可以指定在动态渲染期间将使用的所有颜色附件作为超集,则为渲染通道对象编写的片段着色器可以在移植到此扩展时重复使用而无需修改,只需重新映射附件即可。pColorAttachmentLocations 中的值必须各自唯一。

颜色附件重映射不影响混合状态或格式映射等内容 - 这些始终与渲染通道附件一一对应。这意味着在从渲染通道对象移植时,必须注意确保正确重新排序这些内容,而以前的值会映射到子通道中重新排序的元素。

在发出绘制调用时,位置映射必须在绑定的图形管线和 vkCmdSetRenderingAttachmentLocationsKHR 设置的命令缓冲区状态之间匹配。

当使用辅助命令缓冲区时,VkRenderingAttachmentLocationInfoKHR 也可以链接到 VkCommandBufferInheritanceInfo,以在调用 vkCmdExecuteCommands 时,指定主命令缓冲区中的颜色附件位置映射。如果继承信息中未提供 VkRenderingAttachmentLocationInfoKHR,则等效于提供它,其中 pColorAttachmentLocations 的每个元素的值设置为其在数组中的索引值,颜色附件计数等于 VkCommandBufferInheritanceRenderingInfo::colorAttachmentCount 指定的值。如果在调用 vkCmdExecuteCommands 时存在当前活动的渲染通道实例,则此信息必须在继承信息和状态之间匹配。

此功能主要用于将现有内容移植到新的 API;新应用程序应在渲染通道期间为其着色器中的所有附件保持一致的位置;此功能可以被认为是立即弃用的。

当附件在命令缓冲区状态中映射到 VK_ATTACHMENT_UNUSED 时(通过 vkCmdSetRenderingAttachmentLocationsKHR 或继承状态),不得使用 vkCmdClearAttachments 清除它。某些实现将在发生重映射时更新渲染通道附件绑定,从而使未映射的附件无法通过 vkCmdClearAttachments 将使用的路径写入。这与渲染通道对象一致,在渲染通道对象中,应用程序将无法在当前子通道之外清除附件。

3.4. 输入附件映射

在动态渲染期间,有两种方法可以将输入附件映射到其他附件;最简单的方法是依赖于与相应颜色附件位置匹配的 InputAttachmentIndex 限定符,或者对于深度/模板附件省略。默认情况下,API 中在索引 i 处指定的颜色附件将与 InputAttachmentIndex 等于 i 的输入附件关联。此映射不受 VkRenderingAttachmentLocationInfoKHR 设置的映射的影响。任何没有 InputAttachmentIndex 的输入附件都将与深度/模板附件关联。对于编写新着色器可行的应用程序,这允许简单的映射而无需 API 干预。

对于从渲染通道对象移植现有内容,修改着色器并不简单的应用程序,提供了类似于 VkRenderingAttachmentLocationInfoKHR 的功能,允许将输入附件重新映射到不同的附件。

typedef struct VkRenderingInputAttachmentIndexInfoKHR {
    VkStructureType                 sType;
    const void*                     pNext;
    const uint32_t                  colorAttachmentCount;
    const uint32_t*                 pColorAttachmentInputIndices;
    uint32_t*                       pDepthInputAttachmentIndex;
    uint32_t*                       pStencilInputAttachmentIndex;
} VkRenderingInputAttachmentIndexInfoKHR;

void vkCmdSetRenderingInputAttachmentIndicesKHR(
    VkCommandBuffer                                 commandBuffer,
    const VkRenderingInputAttachmentIndexInfoKHR*   pInputAttachmentIndexInfo);

此信息可以在管线创建期间通过将 VkRenderingInputAttachmentIndexInfoKHR 链接到 VkGraphicsPipelineCreateInfo,在需要片段着色器状态子集时提供。如果在管线创建时未提供此结构,则等效于将 pColorAttachmentInputIndices 的每个元素的值设置为其在数组中的索引值,colorAttachmentCount 设置为 VkPipelineRenderingCreateInfoKHR::colorAttachmentCount 的值,并将 pDepthInputAttachmentIndexpStencilInputAttachmentIndex 设置为 NULL

vkCmdSetRenderingInputAttachmentIndicesKHR 必须仅在动态渲染通道实例中调用。如果未调用此命令,则默认状态是 pColorAttachmentInputIndices 的每个元素的值设置为其在数组中的索引值,并将 pDepthInputAttachmentIndexpStencilInputAttachmentIndex 设置为 NULL

pColorAttachmentInputIndices 的每个元素的索引对应于动态渲染通道中颜色附件的相同索引,并且该元素的值将成为引用它的 InputAttachmentIndex,从而提供了一种将输入附件重新映射到颜色附件的方法。pColorAttachmentInputIndices 中的值必须各自唯一。

如果将 pDepthInputAttachmentIndexpStencilInputAttachmentIndex 中的任何一个设置为 NULL,则意味着只有在着色器未将这些输入附件与 InputAttachmentIndex 关联时,才能在着色器中访问它们。

如果 pDepthInputAttachmentIndexpStencilInputAttachmentIndexpColorAttachmentInputIndices 的任何元素设置为 VK_ATTACHMENT_UNUSED,则表示相应的附件未与输入附件索引关联,并且不能作为着色器中的输入附件访问。

当发出绘制调用时,绑定的图形管线和 vkCmdSetRenderingInputAttachmentIndicesKHR 设置的命令缓冲区状态之间的输入附件索引映射必须匹配。

当使用二级命令缓冲区时,VkRenderingInputAttachmentIndexInfoKHR 也可以链接到 VkCommandBufferInheritanceInfo,以便在调用 vkCmdExecuteCommands 时,指定主命令缓冲区中的输入附件索引映射。如果继承信息中没有提供 VkRenderingInputAttachmentIndexInfoKHR,则等同于提供它,并将 pColorAttachmentInputIndices 的每个元素的值设置为其在数组中的索引值,colorAttachmentCount 设置为 VkCommandBufferInheritanceRenderingInfo::colorAttachmentCount 的值,以及 pDepthInputAttachmentIndexpStencilInputAttachmentIndex 设置为 NULL。如果当前有活动的渲染通道实例,则此信息在调用 vkCmdExecuteCommands 时,必须与继承信息和状态匹配。

提供重新映射功能主要是为了将现有内容移植到新的 API;新的应用程序应该在渲染通道期间,为其着色器中的所有附件一致地设置索引附件索引;此功能可以被视为立即弃用。

3.5. 只读输入附件

渲染通道对象的一个怪癖是,用户可以指定仅用作输入附件的输入附件。对于动态渲染,不能通过将它们标记为上述结构启用的另一种附件类型来指定它们。

与在渲染通道中指定它们不同,由于它们必须与描述符关联,如果 InputAttachmentIndex 没有映射到另一个附件,则实现将无条件地从输入附件描述符中获取值。

一些实现现在可能必须在声明此扩展时提供一个真实的描述符,而他们以前没有这样做 - 这可能会影响诸如 VK_EXT_descriptor_buffer 之类的事情,其中声明了描述符的大小。

3.6. 与 VK_EXT_shader_object 的交互

如果启用 VK_EXT_shader_object,则 vkCmdSetRenderingAttachmentLocationsKHRvkCmdSetRenderingInputAttachmentIndicesKHR 是设置重新映射状态的唯一方法;相应的结构不需要链接到着色器对象的创建,也不需要匹配任何静态状态。

3.7. 与 VK_EXT_rasterization_order_attachment_access 的交互

如果启用 VK_EXT_rasterization_order_attachment_access,则管线深度/模板状态和颜色混合状态位可以与动态渲染一起使用,对输入附件读取的效果与使用渲染通道对象时相同。具体来说,这允许从输入附件进行局部读取,以读取同一渲染通道(甚至同一绘制)内重叠位置的先前片段的值,而无需屏障。此交互不允许在没有屏障的情况下在非附件资源之间进行局部读取。

3.8. GLSL 更改

对 GLSL 进行了小改动,以允许在指定子通道输入时省略 input_attachment_index 限定符。

3.9. HLSL 更改

HLSL 的 SPIR-V 转换当前要求子通道输入在 SubpassInput 变量上指定 vk::input_attachment_index() 属性,这将放松为允许省略它。

4. 示例:移植

通过几行 API 代码更改,应该可以轻松地将大多数使用渲染通道对象的代码移植到使用动态渲染。有一些例外 - 代码将使用比单个子通道或动态渲染限制更多的颜色附件,切换深度/模板附件,或使用非帧缓冲区空间的子通道依赖项,不能以这种方式表达,必须拆分为多个动态渲染通道。例如,以下两段代码指定相同的结果

4.1. 多个子通道

// Write out the setup code.

vkCmdBeginRenderPass2(...);

vkCmdDraw(...);

vkCmdNextSubpass2(...);

vkCmdDraw(...);

vkCmdEndRenderPass2(...);

4.2. 动态渲染依赖项

// Write the setup code

vkCmdBeginRendering(...);

vkCmdDraw(...);

vkCmdPipelineBarrier(...);

vkCmdDraw(...);

vkCmdEndRendering(...);

5. 问题

5.1. 为什么包含颜色附件位置重新排序?

在渲染通道中的多个子通道中,应用程序可以重新关联不同子通道之间的位置,包含此功能是为了能够简单地移植执行此操作的着色器到此扩展。可以省略它,但这需要预处理着色器代码来替换颜色索引以达到相同的效果,如果应用程序尚未设置为执行此操作,这是一个很大的负担。对于开发人员来说,这是一个小的让步,可以使其更容易移植代码,而不会给实现者增加太多负担。

5.2. 为什么不公开多个子通道的某些功能?

这些额外的功能需要实现跳转可能需要在内部拆分渲染通道的环节;此扩展有意限制为所有供应商都可以支持而无需采取这种措施的功能,因为它会大大增加 API 的复杂性,特别是考虑到如果没有专用对象就无法预先计算。

5.3. 是否应该要求输入附件描述符?

一些供应商(包括被认为是分块渲染器的供应商)需要单独的描述符来读取这些图像,没有它们会增加驱动程序的复杂性,并可能降低性能 - 但我们可以重新考虑这一点。

注意:TRANSIENT 附件仍然可以使用此扩展,从而提供了一条避免内存分配的路径,就像使用渲染通道对象一样。

5.4. 此扩展是否应包括片段着色器在渲染期间重新解释颜色/输入附件格式的能力?

提议:单独的扩展。

为了使这项工作正常进行,在颜色输出或输入附件上添加一个简单的修饰,声明该格式被忽略并且写入原始位就足够了,但这可能超出此扩展的范围,并且可能并非所有实现者都支持。这将允许应用程序将使用 OpenGL ES 像素本地存储扩展的代码移植到 Vulkan,并且还可以允许使用比可用附件更多的代码通过别名丢弃的附件来工作(尽管这也可能需要显式的加载/存储命令)。

5.5. 此扩展是否应声明同一绘制调用中片段之间的局部读取?

对于许多供应商来说,这在所有情况下都不是高效或容易实现的。对于支持它的实现,该功能作为与 VK_EXT_rasterization_order_attachment_access 的交互提供。

5.6. 此扩展是否应允许应用程序访问来自附件以外的资源的本地数据?

是的,这允许应用程序在片段之间实现功能时具有更大的灵活性。这不应该是一个重要的实现负担,但如果这个假设被证明是错误的,则可以删除它。

5.7. 是否应在 vkCmdBeginRendering 中指定只读输入附件,以在分块渲染器中启用预取?

这样做会使 API 更加复杂,而收益可能微乎其微。如果还有空间容纳其他附件,应用程序可以通过将此类数据放入一个从不写入的占位符附件中来自己模拟此操作。如果没有空间容纳其他附件,则实现无论如何都无法进行预取。

5.8. 为什么颜色附件位置和输入附件索引的重映射既静态又动态地提供?

要求管道和命令缓冲区之间的此状态匹配与渲染通道对象的工作方式一致。渲染通道对象既在管道中提供,又在开始渲染通道时提供,不同的供应商在不同的时间点使用这些映射。一些供应商修改生成的着色器代码以支持这些映射,而另一些供应商在命令执行时更改硬件状态。为了适应这两种类型的实现,同时在不使用这些映射时不会损害性能,此状态再次需要同时存在于这两个位置。