VK_EXT_multisampled_render_to_single_sampled

本文档指出了在平铺 GPU 上高效多采样渲染的困难,并提出了一个扩展来改进它。

1. 问题陈述

通过仔细使用解析附件,使用 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT 分配的多采样图像内存,loadOp 不等于 VK_ATTACHMENT_LOAD_OP_LOAD,并且 storeOp 不等于 VK_ATTACHMENT_STORE_OP_STORE,在大多数情况下,Vulkan 应用程序能够在平铺 GPU 上高效地执行多采样渲染,而不会产生任何额外的内存开销。

在某些平铺 GPU 上,某些格式的子通道解析操作不能在片上完成,因此会像在子通道之后通过 vkCmdResolveImage 执行解析一样,默默地支付额外的性能和内存成本,而没有向应用程序提供反馈。

此外,在某些情况下,应用程序可能无法在单个渲染通道内完成其多采样渲染;例如,如果它逐帧执行部分光栅化,从上一帧混合图像,或在模拟 GL_EXT_multisampled_render_to_texture。在这种情况下,应用程序可以使用初始子通道从下一个子通道的解析附件有效地加载单采样数据,并填充多采样附件,否则该附件使用 loadOp 等于 VK_ATTACHMENT_LOAD_OP_DONT_CARE。但是,这并非总是可能(例如,对于在缺少 VK_EXT_shader_stencil_export 的情况下的模板),并且存在多个缺点。

一些实现能够在硬件中高效地执行所述操作,从而有效地从单采样附件的内容加载多采样附件。结合在子通道末尾执行解析操作的能力,这些实现能够在单采样附件上执行多采样渲染,而无需额外的内存或带宽开销。

本文档提出了一个扩展,该扩展通过允许帧缓冲和渲染通道包含单采样附件,同时使用指定的采样数进行渲染,从而暴露了此功能。

2. 提案

该扩展首先允许帧缓冲包含单采样和多采样附件的混合。在缺少 VkMultisampledRenderToSingleSampledInfoEXT 的情况下,使用 N 个采样执行多采样渲染的渲染通道子通道仍然需要子通道中使用的所有附件都具有 N 个采样。与 VK_EXT_dynamic_rendering 类似,如果存在 VkMultisampledRenderToSingleSampledInfoEXT,则附件可以是单采样和多采样的混合。

在下文中,“通道”指的是渲染通道子通道或 VK_EXT_dynamic_rendering 渲染通道。

当提供 VkMultisampledRenderToSingleSampledInfoEXT 时,指定使用 N 个采样进行渲染,则通道中使用的任何附件都可以具有一个或 N 个采样。在这种情况下,具有一个采样的附件将在通道期间自动加载为多采样(其中每个像素的值都复制到片上内存中该像素的所有采样中),并且将在通道结束时自动解析。本文档将此类单采样附件称为多采样渲染到单采样附件。

此外,此扩展还为应用程序提供了一种方法,以确定在通道解析操作期间使用附件格式是否会对性能产生不利影响,这可能会特别不利地影响多采样渲染到单采样通道。

此 API 引入了以下内容:

功能,用于声明实现是否支持多采样渲染到单采样

typedef struct VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT {
    VkStructureType    sType;
    void*              pNext;
    VkBool32           multisampledRenderToSingleSampled;
} VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT;

性能查询,用于指定在通道结束时使用具有某种格式的附件进行解析是否在硬件上是最佳的

typedef struct VkSubpassResolvePerformanceQueryEXT {
    VkStructureType               sType;
    void*                         pNext;
    VkBool32                      optimal;
} VkSubpassResolvePerformanceQueryEXT;

指定通道应使用 N 个采样计数执行多采样渲染到单采样(扩展 VkSubpassDescription2VkRenderingInfo

typedef struct VkMultisampledRenderToSingleSampledInfoEXT {
    VkStructureType               sType;
    void*                         pNext;
    VkBool32                      multisampledRenderToSingleSampledEnable;
    VkSampleCountFlagBits         rasterizationSamples;
} VkMultisampledRenderToSingleSampledInfoEXT;

一个图像创建标志,用于指示在多采样渲染到单采样通道中使用单采样图像的意图

VK_IMAGE_CREATE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_BIT_EXT

在具有 N 个采样的多采样渲染到单采样通道中,所有渲染都使用 N 个采样完成,就像任何单采样附件实际上都有 N 个采样一样。这意味着 VkPipelineMultisampleStateCreateInfo::rasterizationSamples 必须为 N,并且光栅化与 Vulkan 不使用此扩展的通道的多采样规则相同。因此,此扩展中的功能纯粹影响单采样附件的加载和存储,以及它们在通道期间自动表示为多采样的方式。

无论使用哪种加载和存储操作,多采样渲染到单采样通道中的单采样附件都表示为多采样。不同的加载和存储操作的行为与使用多采样附件的情况相同。以下内容阐明了与多采样渲染到单采样附件结合使用的操作:

  • VK_ATTACHMENT_LOAD_OP_LOAD:对于每个像素,其值在通道开始时被复制到所有 N 个相应的采样中。

  • VK_ATTACHMENT_LOAD_OP_CLEAR:附件的多采样表示被清除,而不是单采样附件。

  • VK_ATTACHMENT_LOAD_OP_DONT_CARE:指定不需要保留单采样附件的先前内容,并且附件的多采样表示的内容将是未定义的。

  • VK_ATTACHMENT_LOAD_OP_NONE_EXT:指定将保留单采样附件的先前内容,但附件的多采样表示的内容将是未定义的。

  • VK_ATTACHMENT_STORE_OP_STORE:渲染结果在通道结束时自动解析到单采样附件中,并丢弃多采样数据。对于渲染通道,如果子通道随后从附件读取作为多采样渲染到单采样输入附件,则不确定返回的是先前子通道的多采样数据还是解析后的值。

  • VK_ATTACHMENT_STORE_OP_DONT_CARE:指定渲染后不需要多采样内容,并且可以将其丢弃。单采样附件的内容将是未定义的。

  • VK_ATTACHMENT_STORE_OP_NONE_KHR:指定存储操作不访问单采样附件的内容,但如果在通道期间写入了附件,则其内容将是未定义的。

尽管此扩展添加了对具有某种格式的附件的解析性能的查询,但结果不仅限于多采样渲染到单采样通道,而且还适用于具有单独的多采样和单采样附件以及解析操作的通道。

3. 示例

确定某种格式是否适合用作多采样渲染到单采样附件以获得最佳性能

VkSubpassResolvePerformanceQueryEXT perfQuery = {
    .sType = VK_STRUCTURE_TYPE_SUBPASS_RESOLVE_PERFORMANCE_QUERY_EXT,
};

VkFormatProperties2 formatProperties = {
    .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
    .pNext = &perfQuery;
};

vkGetPhysicalDeviceFormatProperties2(device, format, &formatProperties);

创建具有 4 个采样的多采样渲染到单采样子通道的渲染通道

// Render pass attachments with mixed sample count
VkAttachmentDescription2 attachmentDescs[3] = {
    [0] = {
        .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR,
        .format = ...,
        .samples = 1,
        .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
        .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
        .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
        .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
        .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
    },
    [1] = {
        .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR,
        .format = ...,
        .samples = 4,
        .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
        .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
        .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
        .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
        .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
        .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
    },
    [2] = {
        .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR,
        .format = ...,
        .samples = 1,
        .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
        .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
        .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
        .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
    },
};

// Subpass attachment references
VkAttachmentReference2 colorAttachments[2] = {
    [0] = {
        .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
        .attachment = 0,
        .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
    },
    [1] = {
        .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
        .attachment = 1,
        .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
    },
};

VkAttachmentReference2 depthStencilAttachment = {
    .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
    .attachment = 0,
    .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
    .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
};

// Multisampled-render-to-single-sampling info.  Rendering at 4xMSAA.
VkMultisampledRenderToSingleSampledInfoEXT msrtss = {
    .sType = VK_STRUCTURE_TYPE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_INFO_EXT,
    .multisampledRenderToSingleSampledEnable = VK_TRUE,
    .rasterizationSamples = 4,
};

// Resolve modes for depth/stencil
VkSubpassDescriptionDepthStencilResolve depthStencilResolve = {
    .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE,
    .pNext = &msrtss,
    .depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT,
    .stencilResolveMode = VK_RESOLVE_MODE_NONE,
};

// The subpass description where multisampled-render-to-single-sampled rendering is enabled.
VkSubpassDescription2 subpassDescription = {
    .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR,
    .pNext = &depthStencilResolve,
    .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
    .colorAttachmentCount = 2,
    .pColorAttachments = colorAttachments,
    .pDepthStencilAttachment = &depthStencilAttachment,
};

// The render pass creation.
VkRenderPassCreateInfo2KHR renderPassInfo = {
    .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR,
    .attachmentCount = 3,
    .pAttachments = attachmentDescs,
    .subpassCount = 1,
    .pSubpasses = &subpassDescription,
};

VkRenderPass renderPass;
vkCreateRenderPass2(device, &renderPassInfo, NULL, &renderPass);

使用 VK_KHR_dynamic_rendering 的类似通道

VkRenderingAttachmentInfo colorAttachments[2] = {
    // Assuming a single-sampled color attachment 0
    {
        .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO
        .imageView = ...,
        .imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
        .resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT,
        .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    },
    // Assuming a multisampled color attachment 1 with 4x samples
    {
        .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO
        .imageView = ...,
        .imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
        .resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT,
        .resolveImageView = ...,
        .resolveImageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
        .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    },
};

// Assuming a single-sampled depth/stencil attachment
VkRenderingAttachmentInfo depthAttachment = {
    .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO
    .imageView = ...,
    .imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
    .resolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT,
    .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
    .clearValue = { ... },
};
VkRenderingAttachmentInfo stencilAttachment = {
    .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO
    .imageView = ...,
    .imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
    .resolveMode = VK_RESOLVE_MODE_NONE,
    .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
    .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
};

// Multisampled-render-to-single-sampling info.  Rendering at 4xMSAA.
VkMultisampledRenderToSingleSampledInfoEXT msrtss = {
    .sType = VK_STRUCTURE_TYPE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_INFO_EXT,
    .multisampledRenderToSingleSampledEnable = VK_TRUE,
    .rasterizationSamples = 4,
};

VkRenderingInfo renderingInfo = {
    .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
    .pNext = &msrtss,
    .renderArea = { ... },
    .layerCount = 1,
    .colorAttachmentCount = 2,
    .pColorAttachments = colorAttachments,
    .pDepthAttachment = &depthAttachment,
    .pStencilAttachment = &stencilAttachment,
};

vkCmdBeginRendering(commandBuffer, &renderingInfo);

4. 问题

4.1. 已解决:关于 VK_KHR_dynamic_rendering 呢?

对于平铺 GPU,渲染通道仍然是最佳解决方案。VK_KHR_dynamic_rendering 扩展在平铺 GPU 上的当前限制可能会随着时间的推移而改善,因此此扩展可以与动态渲染一起使用。

4.2. 已解决:某些格式缺乏片上解析支持将对此扩展产生特别不利的影响。可以添加格式功能标志吗?

添加了一个特定的结构来查询每种格式的子通道解析性能。避免使用格式功能标志的原因有两个;一个是它们的稀缺性,另一个是通常格式功能标志意味着如果缺少该标志,则不允许使用相应的功能。然而,在这种情况下,实现必然会支持子通道解析,尽管效率较低,因此缺少这样的假设格式功能标志不会阻止它们的使用。