Vulkan API – VRS

Vulkan API – VRS Vulkan API是一种低阶绘图API,用于开发高性能应用程序和游戏,具有特定的扩展 (extension) 可让开发人员使用VRS。这个扩展称为VK_KHR_fragment_shading_rate,它允许开发人员对每个绘制调用 (draw-call)、图形单元 (primitive)、或屏幕区域 (screen region) 设置着色率。 VRS扩展受到现代GPU的广泛支持,包括ARM Mali GPU,使其为开发人员创建先进和优化的图形应用程序的有用技术。

 

以下图片描述了Vulkan APIVRS扩展的高层次概述。开发人员可以通过管线对象 (pipeline objects)、动态状态 (dynamic state)、着色器和帧缓存对象 (framebuffer objects) 指定着色率和结合器运算子 (combiner ops)操作。

 

 

 Vulkan VRS API – 程式码模型

 

查询VRS功能

在正确使用VRS之前,开发人员需要了解相关的硬体能力。该扩展提供了API结构和函数,允许开发人员查询与VRS相关的设备特性。

 

最佳实践建议:

  • 在使用VRS之前,首先检查设备特性
  • 根据需要启用VRS功能
  • 当同时使用MSAAVRS时,查询特定采样率支援的着色率

 

检查可用性

要检查VRS是否可用于该硬件,开发人员必须执行以下检查

使用vkEnumerateDeviceExtensionProperties检查以下扩充功能的可用性

VK_KHR_fragment_shading_rate

VK_KHR_get_physical_device_properties2 (适配到 Vulkan 1.1)

VK_KHR_create_renderpass2 (适配到 Vulkan 1.2)

用于附件着色率(每个区域) (必需)

用于管线或图元着色率 (非必需)

使用vkGetPhysicalDeviceFeatures2查询可用的VRS功能

透过VkPhysicalDeviceFeatures2结构的pNext 指标,设定VkPhysicalDeviceFragmentShadingRateFeaturesKHR

查询后的VkPhysicalDeviceFragmentShadingRateFeaturesKHR可以传递给VkDeviceCreateInfo以创建支持VRS的设备

 

 查询物理设备属性

要检查硬件的VRS功能,开发人员必须通过vkGetPhysicalDeviceProperties2获取VkPhysicalDeviceFragmentShadingRatePropertiesKHR

 返回的结构包含在应用程序/游戏中使用VRS的重要信息。

 

查询可用的着色率

尽管返回的VkPhysicalDeviceFragmentShadingRatePropertiesKHR结构包含可用的着色率,但不是精确的。开发人员必须通过vkGetPhysicalDeviceFragmentShadingRatesKHR查询可用的着色率和采样率组合。

 该函数应该被调用两次

获取可用着色率的数量

根据数量,获取可用着色率的数组

 

返回数组的每个元素都是结构类型

 sampleCounts是兼容采样率的位集 (例如:0b00000101 => 着色率可以与NoAA 1xMSAA 4x一起使用)

fragmentSize是着色率的大小 (例如:2x1代表着色率的宽度为2,高度为1

 

最佳实践建议:

  • 1x1 着色率(VRS OFF)下的 sampleCounts 总是 0xFFFFFFF~0)。开发人员必须将它与 framebufferColorSampleCountsVkPhysicalDeviceLimits)取交集来过滤实际可用的采样率

启用 VRS 功能

建立设备时启用 VRS 功能

启用 3 个扩展:
VK_KHR_fragment_shading_rate
VK_KHR_create_renderpass2 (适配到 Vulkan 1.2)
VK_KHR_get_physical_device_properties2 (适配到 Vulkan 1.1)

填充 VkPhysicalDeviceFragmentShadingRateFeaturesKHR 以启用 VRS 功能:
pipelineFragmentShadingRate = true false
primitiveFragmentShadingRate = true false
attachmentFragmentShadingRate = true false

VkPhysicalDeviceFragmentShadingRateFeaturesKHR 添加到 VkPhysicalDeviceFeatures2 VkDeviceCreateInfo pNext

 

指定管线着色率和结合器操作

开发人员可以以下列两种方式之一指定管线着色率和结合器操作(第 錯誤! 找不到參照來源。 节):

管线状态对象 (Pipeline State Object)
在创建图形管线状态对象时,使用 VkPipelineFragmentShadingRateStateCreateInfoKHR 扩展 VkGraphicsPipelineCreateInfo 参见第 2.3.1

动态状态(Dynamic State)
在录制命令时调用 vkCmdSetFragmentShadingRateKHR 以更改当前图形管线的动态状态 参见第 錯誤! 找不到參照來源。

 

最佳实践建议:

  • 如果禁用了管线着色率(pipelineFragmentShadingRate == false),则片段大小必须为 1x1
  • 如果禁用了图元着色率(primitiveFragmentShadingRate == false),则第一个结合器参数(combinerOps[0])必须是 KEEP
  • 如果禁用了附件着色率(attachmentFragmentShadingRate == false),则第二个结合器参数 combinerOps[1])必须是 KEEP
  • 如果管线创建信息中不包含 VkPipelineFragmentShadingRateStateCreateInfo VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR,则 VRS 状态就像关闭一样,会变成
  • 管线着色率1x1
  • 结合器参数组KEEP, KEEP

透过管线状态对象设置管线着色率

开发人员可以通过使用 VkPipelineFragmentShadingRateStateCreateInfo 结构将 VkGraphicsPipelineCreateInfo 结构扩展,以指定管线着色率和结合器操作:

 fragmentSize:指定片段着色的宽度和高度

combinerOps:两个结合器运算符描述如何合并三种着色率(将在第 錯誤! 找不到參照來源。 节中说明)

 

透过动态状态设置管线着色率

如果开发人员想要通过指令缓冲 (command buffer) 来控制管线着色率和结合器运算符,他们必须将 VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR 添加到 VkGraphicsPipelineCreateInfo 中的 VkPipelineDynamicStateCreateInfo 结构中。

 

然后,开发人员可以通过调用以下 API来控制管线着色率和结合器操作:

 fragmentSize:指定片段着色的宽度和高度的大小

combinerOps:两个结合器运算符描述如何将三种着色率合并(将在第錯誤! 找不到參照來源。节中说明)

 

结合器运算符 (Combiner Ops)

根据 Figure 2‑1:  所描述的,GPU会从三种着色率(管线、图元和附件着色率)和两个指定的结合器运算符获取有效着色率。第一个结合器会将管线着色率和图元着色率结合在一起,然后第二个结合器会将第一个结合器的结果和附件着色率结合在一起。开发者可以从以下列表中选择两个结合器的结合运算:

 

 VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR => 选择A

VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR => 选择 B

VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_KHR => AB的片段宽/高,各别取元素最小值者

VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR => AB的片段宽/高,各别取元素最大值者

VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR => AB的片段宽/高,各别元素相乘

 

举例来说,若管线、图元和附件的着色率分别为1x22x14x4,结合器的运算分别为MAXMIN。第一个结合器会将管线着色率(A)和图元着色率(B)用MAX运算结合,所以第一个结果为2x2。然后,第二个结合器会将第一个结果(A)和附件着色率(B)用MIN运算结合,所以最终着色率为2x2

 

如果最终着色率不受GPU支持,GPU将会从受支持的着色率列表中自动选择一个着色率替代。

 

最佳实践建议:

  • 正确指定结合器运算符。当VRS关闭时,所有结合器运算应该都为KEEP
  • 如果图元着色率被禁用,则第一个结合器运算必须是KEEP
  • 如果附件着色率被禁用,则第二个结合器运算必须是KEEP
  • 如果硬件不支持结合器,将会自动对其进行限制
  • 开发者可以在片段着色器中检查有效着色率(请参阅2.608D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100320039003500390037003000350031000000 节)

指定图元着色率

开发者可以在光栅化 (rasterization) 之前的最后一个激活着色器中指定每个图元的着色率。 GLSL语言定义了一个内建的输出变量gl_PrimitiveShadingRateEXT来储存当前着色器呼叫该图元的图元着色率。

 

以下顶点着色器示例将一个整数值写入内建输出变量gl_PrimitiveShadingRateEXT中,以指定当前顶点的图元着色率:

#version 310 es

#extension GL_EXT_fragment_shading_rate : require

 

// ...

 

void main() {

    // ...

    gl_Position = pos;

    gl_PrimitiveShadingRateEXT = 0x5; // 2x2 shading rate

}

 

 

片段大小

二进制值

十六进制值

D9200 是否支持

1x1

0000

0

1x2

0001

1

1x4

0010

2

2x1

0100

4

2x2

0101

5

2x4

0110

6

4x1

1000

8

4x2

1001

9

4x4

1010

A

gl_PrimitiveShadingRateEXT 的有效值

 

最佳实践建议:

  • 如果开发人员打算使用图元着色率,他们必须在建立装置时启用对应的功能(VkPhysicalDeviceFragmentShadingRateFeaturesKHR 中的 primitiveFragmentShadingRate
  • 写入 gl_PrimitiveShadingRateKHR 的着色器必须是在光栅化之前的最后一个激活着色器阶段
  • 例如:
  • 顶点着色器,片段着色器) => 顶点着色器需写入图元着色率
  • (顶点着色器,几何着色器,片段着色器)=> 几何着色器需写入图元着色率
  • (顶点着色器,曲面细分着色器,片段着色器) => 曲面细分着色器需写入图元着色率
  • (顶点着色器,曲面细分着色器,几何着色器,片段着色器) => 几何着色器需写入图元着色率
  • 如果不这样做,将会忽略所写的图元着色率
  • 着色器代码必须启用扩展GL_EXT_fragment_shading_rate
  • 实际的图元着色率由每个基本图元的驱动顶点决定

指定附件着色率

要指定附件着色率,开发人员必须创建一个特殊的附件贴图并将其附加到帧缓冲器。根据以下原则,着色率附件中对应的纹理元素,对帧缓冲器中的每个像素指定附件着色率:

x’ = Floor (x / TexelSize.width)

y’ = Floor (y / TexelSize.width)

其中,x’y’是着色率附件中的纹理元素的坐标,xy是帧缓冲器中像素的坐标,TexelSize是每个纹理元素对应的区域大小

 

 

值得注意的是,开发人员必须首先选择TexelSize。具体而言,开发人员可以检查结构VkPhysicalDeviceFragmentShadingRateProperites的三个成员:

VkExtent2D minFragmentShadingRateAttachmentTexelSize

VkExtent2D maxFragmentShadingRateAttachmentTexelSize

uint32_t maxFragmentShadingRateAttachmentTexelSizeAspectRatio

 

从这三个成员数组中,开发人员可以根据以下规则选择有效的纹理元素大小:

texelSize.width >= minFragmentShadingRateAttachmentTexelSize.width

texelSize.width <= maxFragmentShadingRateAttachmentTexelSize.width

texelSize.width 必须是二的幂次方 (i.e., 2n)

texelSize.height >= minFragmentShadingRateAttachmentTexelSize.height

texelSize.height <= maxFragmentShadingRateAttachmentTexelSize.height

texelSize.height 必须是二的幂次方 (i.e., 2n)

(texelSize.width / texelSize.height) <= maxFragmentShadingRateAttachmentTexelSizeAspectRatio

(texelSize.height / texelSize.width) <= maxFragmentShadingRateAttachmentTexelSizeAspectRatio

 

附件着色率将编码为纹理元素值,如 Table 2‑2: 所述:

片段大小

二進制值

十六進制值

D9200 是否支持

1x1

0000

0

1x2

0001

1

1x4

0010

2

2x1

0100

4

2x2

0101

5

2x4

0110

6

4x1

1000

8

4x2

1001

9

4x4

1010

A

着色率附件图像的有效纹理值

 

最佳实践建议:

  • 如果开发人员打算使用附件着色率,他们必须在建立装置时启用对应的功能 (VkPhysicalDeviceFragmentShadingRateFeaturesKHR 中的 attachmentFragmentShadingRate)
  • 在创建缓冲着色率附件贴图之前,开发人员必须先查询支持的格式和排列方式
    • D9200上,必须以VK_FORMAT_R8_UINTVK_TILING_OPTIMAL创建缓冲着色率附件图像
  • 开发人员在创建着色率附件贴图之前必须查询支持的像素大小。
    • D9200上,仅支持8x816x1632x32
  • 要注意读取着色率附件可能带来的额外开销以及由于资源更新而导致的额外同步成本。
  • 开发人员必须处理着色率附件贴图的同步问题(请参阅第2.5.308D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100320039003500390036003700390031000000 节)。

 

创建着色率附件贴图

若要建立将会用于着色率附件的 VkImage,开发人员必须正确地指定 VkImageCreateInfo 结构的成员值:

imageType: 必须为 VK_IMAGE_TYPE_2D

samples: 必须为 VK_SAMPLE_COUNT_1_BIT

format: 格式必须支持 VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR
(请参阅 vkGetPhysicalDeviceFormatProperties

D9200仅支持VK_FORMAT_R8_UINT

width: ceil(framebuffer width / texelSize.width)

height: ceil(framebuffer height / texelSize.height)

usage: 位元标签必须包含 VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR

tiling: 排列方式 + 格式必须支持 VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR
(请参阅vkGetPhysicalDeviceFormatProperties

D9200仅支持VK_TILING_OPTIMAL

 

另外,VkImageView 必须使用正确的 VkImageViewCreateInfo 结构成员值建立:

viewType: 必须为 VK_IMAGE_VIEW_TYPE_2D or VK_IMAGE_VIEW_TYPE_2D_ARRAY

format: 必须与 VkImageCreateInfo 结构的格式成员相同

 

设置渲染通道 (renderpass) 和帧缓冲 (framebuffer)

(重要提示:为了将着色率附加到渲染通道,开发人员必须使用vkCreateRenderPass2,而不是vkCreateRenderPass

 

开发人员必须扩展VkRenderPassCreateInfo2中的VkSubpassDescription2,使用新结构VkFragmentShadingRateAttachmentInfoKHR指定着色率附件。

 VkAttachmentReference2 pFragmentShadingRateAttachment

layout 必须为 VK_IMAGE_LAYOUT_GENERAL VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR

VkExtent2D shadingRateAttachmentTexelSize: 着色率附件的像素大小

 

着色率附件的同步

开发人员可以每帧更新著色率附件贴图来动态控制基于区域的VRS。如果这样做,开发人员必须意识到着色率附件贴图的资源同步问题。

 

示例:管线屏障 (Pipeline Barrier)

通过计算着色器 (compute shader) 更新贴图,然后作为着色率附件使用:

 

需要在两个指令之间,透过调用vkCmdPipelineBarrier插入屏障:

vkCmdDispatch(...); // command 0

vkCmdPipelineBarrier(...); // <== the inserted pipeline barrier

vkCmdBeginRenderPass(...);

vkCmdDraw(...); // command 1

vkCmdEndRenderPass(...);

 

 

示例:子通道 (Subpass) 依赖关系

另一个示例是,贴图是由同一渲染通道中的第一个子通道更新,并由第二个子通道使用:

 

为了解决同步问题,开发人员必须正确指定子通道依赖关系:

 

查询片段着色器中的有效着色率

为了要获取GPU在片段着色器中使用的有效着色率,开发人员可以在片段着色器中读取一个内建变量gl_ShadingRateEXT。该变量的资料表示与gl_PrimitiveShadingRateEXT相同。开发人员可以使用这些值来视觉化实际的着色率,以便进行调试。

 

#version 310 es

#extension GL_EXT_fragment_shading_rate : require

 

// ...

 

void main() {

    // ...

    if (gl_ShadingRateEXT != 0) { // VRS is on

        // ...

    } else { // VRS is off

        // ...

    }

}

 

最佳实践建议:

  • 该着色器必须是片段着色器
  • 该着色器代码必须启用扩展GL_EXT_fragment_shading_rate