非规范 SPIR-V 映射

本附录包括

  • 对 Vulkan 和 OpenGL 中使用和不使用 SPIR-V 的特性差异进行比较

  • 讨论 GLSL 特性如何逻辑地映射到 SPIR-V 特性。

特性比较

以下特性在 OpenGL 和 Vulkan 中都被删除

  • 子程序

  • 共享和打包块布局

  • 已弃用的纹理函数(例如,texture2D()

  • 已弃用的噪声函数(例如,noise1()

  • 兼容性配置文件特性

  • gl_DepthRangeParametersgl_NumSamples

Vulkan 删除了以下特性,这些特性仍然存在于 OpenGL 中

  • 非不透明类型的默认 uniform:UniformConstant 存储类可以在全局作用域的单个变量上使用。(也就是说,uniform 不必在块中,除非它们是 GLSL 4.5 或更高版本中块的内置成员。)

  • GLSL 原子计数器绑定具有 offset 布局限定符 → SPIR-V AtomicCounter 存储类使用 Offset 修饰符

  • GLSL origin_lower_left → SPIR-V OriginLowerLeft

  • 顶点着色器中输入双精度数的特殊位置规则

  • gl_VertexIDgl_InstanceID(更多细节如下)

以下特性在 OpenGL 和 Vulkan 中都添加了

  • 特化常量

  • offset 可以以与声明顺序不同的顺序组织成员

  • offsetalign 布局限定符,用于不支持它们的 uniform/buffer 块版本

仅限 Vulkan:添加了以下特性

  • 推送常量缓冲区

  • 单独的纹理和采样器的着色器组合(SPIR-V OpTypeSampler

  • 描述符集(如果存在,DescriptorSet 必须为 0)

  • gl_VertexIndexgl_InstanceIndex

  • 子通道输入目标和输入附件(input_attachment_index

以下特性在 OpenGL 和 Vulkan 中都发生了更改

  • gl_FragColor 将不再指示隐式广播

仅限 Vulkan:以下特性发生了更改

  • 精度限定符(mediumplowp)将适用于所有版本,而不是为桌面版本删除(桌面版本的默认精度对于所有类型都是 highp

  • uniform 和缓冲块的数组对整个对象只采用一个绑定编号,而不是每个数组元素一个

  • 默认原点是 origin_upper_left 而不是 origin_lower_left

Vulkan 不允许在其 SPIR-V 环境规范中使用诸如 UBO 和 SSBO 之类的资源的多维数组。SPIR-V 支持它,OpenGL 已经允许 GLSL 着色器这样做。OpenGL 的 SPIR-V 也允许它。

从 GLSL 到 SPIR-V 的映射

特化常量

可以使用 layout(constant_id=…​) 声明 SPIR-V 特化常量,这些常量可以稍后由客户端 API 设置。例如,要创建默认值为 12 的特化常量

layout(constant_id = 17) const int arraySize = 12;

以上,17 是 API 或其他工具稍后可以引用此特定特化常量的 ID。API 或中间工具可以在将其完全降低为可执行代码之前将其值更改为另一个常量整数。如果在最终降低之前从未更改过,它将保留值 12。

特化常量具有 const 语义,但它们不折叠。因此,可以使用上面的 arraySize 声明数组

vec4 data[arraySize];  // legal, even though arraySize might change

特化常量可以在表达式中

vec4 data2[arraySize + 2];

这将使 data2 的大小比 arraySize 在将着色器降低为可执行代码时具有的任何常量值大 2。

由特化常量形成的表达式在着色器中的行为也像一个特化常量,而不是像一个常量。

arraySize + 2       // a specialization constant (with no constant_id)

此类表达式可以在与常量相同的地方使用。

constant_id 只能应用于标量整数、标量浮点数或标量布尔值。

只有基本运算符和构造函数才能应用于特化常量,并且仍然产生特化常量

layout(constant_id = 17) const int arraySize = 12;
sin(float(arraySize));    // result is not a specialization constant

虽然 SPIR-V 特化常量仅适用于标量,但可以通过对标量进行运算来创建向量

layout(constant_id = 18) const int scX = 1;
layout(constant_id = 19) const int scZ = 1;
const ivec3 scVec = ivec3(scX, 1, scZ);  // partially specialized vector

内置变量可以附加一个 constant_id

layout(constant_id = 18) gl_MaxImageUnits;

这使其表现为特化常量。这不是完全的重新声明;所有其他特性都从原始内置声明中保留下来。

可以使用应用于 in 限定符的特殊布局 local_size_{xyz}_id 来特化内置向量 gl_WorkGroupSize。例如

layout(local_size_x_id = 18, local_size_z_id = 19) in;

这使得 gl_WorkGroupSize.y 成为一个非特化常量,其中 gl_WorkGroupSize 是一个部分特化的向量。它的 xz 分量可以使用 ID 18 和 19 稍后进行特化。

仅限 Vulkan:推送常量

推送常量驻留在使用应用于 uniform-block 声明的新布局限定符 ID push_constant 声明的 uniform 块中。API 将一组常量写入推送常量缓冲区,并且着色器从 push_constant 块读取它们

layout(push_constant) uniform BlockName {
    int member1;
    float member2;
    ...
} InstanceName; // optional instance name
... = InstanceName.member2; // read a push constant

用于 push_constant uniform 块的内存核算与用于其他 uniform 块的内存核算不同:它必须适合的内存池是单独的小池。默认情况下,push_constant 缓冲区遵循 std430 打包规则。

仅限 Vulkan:描述符集

描述符集中的每个着色器资源都被分配一个(集编号、绑定编号、数组元素)的元组,该元组定义了它在描述符集布局中的位置。在 GLSL 中,集编号和绑定编号分别通过 setbinding 布局限定符分配,并且数组元素是隐式地连续分配的,从数组的第一个元素的索引等于零开始(非数组变量的数组元素为零)

// Assign set number = M, binding number = N, array element = 0
layout (set=M, binding=N) uniform sampler2D variableName;
// Assign set number = M, binding number = N for all array elements,
// and array element = i for the ith member of an array of size I.
layout (set=M, binding=N) uniform sampler2D variableNameArray[I];

例如,可以在两个不同的描述符集中声明两个组合的纹理/采样器对象,如下所示

layout(set = 0, binding = 0) uniform sampler2D ts3;
layout(set = 1, binding = 0) uniform sampler2D ts4;

有关描述符集操作模型的更多详细信息,请参阅 API 文档。

仅限 Vulkan:采样器、图像、纹理和缓冲区

存储图像

存储图像在 GLSL 着色器源中使用适当维度的 uniform 图像变量以及格式布局限定符(如果需要)进行声明

layout (set=m, binding=n, r32f) uniform image2D myStorageImage;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "myStorageImage"
        OpDecorate %9 DescriptorSet m
        OpDecorate %9 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 2D 0 0 0 2 R32f
%8 = OpTypePointer UniformConstant %7
%9 = OpVariable %8 UniformConstant
        ...

采样器

SPIR-V 采样器在 GLSL 着色器源代码中使用 uniform samplersamplerShadow 类型声明。

layout (set=m, binding=n) uniform sampler mySampler;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %8 "mySampler"
        OpDecorate %8 DescriptorSet m
        OpDecorate %8 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeSampler
%7 = OpTypePointer UniformConstant %6
%8 = OpVariable %7 UniformConstant
        ...

纹理(采样图像)

纹理在 GLSL 着色器源代码中使用具有适当维度的 uniform 纹理变量声明。

layout (set=m, binding=n) uniform texture2D mySampledImage;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "mySampledImage"
        OpDecorate %9 DescriptorSet m
        OpDecorate %9 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 2D 0 0 0 1 Unknown
%8 = OpTypePointer UniformConstant %7
%9 = OpVariable %8 UniformConstant
        ...

组合纹理和采样器

组合纹理和采样器在 GLSL 着色器源代码中使用具有适当维度的 uniform 纹理组合采样器变量声明。

layout (set=m, binding=n) uniform sampler2D myCombinedImageSampler;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %10 "myCombinedImageSampler"
        OpDecorate %10 DescriptorSet m
        OpDecorate %10 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 2D 0 0 0 1 Unknown
%8 = OpTypeSampledImage %7
%9 = OpTypePointer UniformConstant %8
%10 = OpVariable %9 UniformConstant
        ...

请注意,根据上述章节,组合图像采样器描述符在着色器中可以仅被称为图像或采样器。

组合独立的采样器和纹理

使用关键字 sampler 声明的采样器仅包含过滤信息,不包含纹理或图像。

uniform sampler s;    // a handle to filtering information

使用诸如 texture2D 等关键字声明的纹理仅包含图像信息,不包含过滤信息。

uniform texture2D t;  // a handle to a texture (an image in SPIR-V)

然后可以使用构造函数在进行纹理查找调用时组合采样器和纹理。

texture(sampler2D(t, s), ...);

请注意,为了清楚地说明此功能,上面省略了 layout() 信息。

纹理缓冲区(Uniform 纹素缓冲区)

纹理缓冲区在 GLSL 着色器源代码中使用 uniform textureBuffer 变量声明。

layout (set=m, binding=n) uniform textureBuffer myUniformTexelBuffer;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "myUniformTexelBuffer"
        OpDecorate %9 DescriptorSet m
        OpDecorate %9 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 Buffer 0 0 0 1 Unknown
%8 = OpTypePointer UniformConstant %7
%9 = OpVariable %8 UniformConstant
        ...

图像缓冲区(存储纹素缓冲区)

图像缓冲区在 GLSL 着色器源代码中使用 uniform imageBuffer 变量声明。

layout (set=m, binding=n, r32f) uniform imageBuffer myStorageTexelBuffer;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "myStorageTexelBuffer"
        OpDecorate %9 DescriptorSet m
        OpDecorate %9 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 Buffer 0 0 0 2 R32f
%8 = OpTypePointer UniformConstant %7
%9 = OpVariable %8 UniformConstant
        ...

存储缓冲区

存储缓冲区在 GLSL 着色器源代码中使用 buffer 存储限定符和块语法声明。

layout (set=m, binding=n) buffer myStorageBuffer
{
    vec4 myElement[];
};

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "myStorageBuffer"
        OpMemberName %9 0 "myElement"
        OpName %11 ""
        OpDecorate %8 ArrayStride 16
        OpMemberDecorate %9 0 Offset 0
        OpDecorate %9 BufferBlock
        OpDecorate %11 DescriptorSet m
        OpDecorate %11 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypeRuntimeArray %7
%9 = OpTypeStruct %8
%10 = OpTypePointer Uniform %9
%11 = OpVariable %10 Uniform
        ...

Uniform 缓冲区

Uniform 缓冲区在 GLSL 着色器源代码中使用 uniform 存储限定符和块语法声明。

layout (set=m, binding=n) uniform myUniformBuffer
{
    vec4 myElement[32];
};

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %11 "myUniformBuffer"
        OpMemberName %11 0 "myElement"
        OpName %13 ""
        OpDecorate %10 ArrayStride 16
        OpMemberDecorate %11 0 Offset 0
        OpDecorate %11 Block
        OpDecorate %13 DescriptorSet m
        OpDecorate %13 Binding n
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypeInt 32 0
%9 = OpConstant %8 32
%10 = OpTypeArray %7 %9
%11 = OpTypeStruct %10
%12 = OpTypePointer Uniform %11
%13 = OpVariable %12 Uniform
        ...

子通道输入

在渲染通道中,子通道可以将结果写入输出目标,然后下一个子通道可以将其读取为输入子通道。“子通道输入”功能涉及读取输出目标的能力。

子通道输入通过一组新的类型读取,仅适用于片段着色器。

subpassInput
subpassInputMS
isubpassInput
isubpassInputMS
usubpassInput
usubpassInputMS

与采样器和图像对象不同,子通道输入由片段的 (x, y, layer) 坐标隐式寻址。

输入附件除了描述符集和绑定编号外,还用其输入附件索引进行装饰。

layout (input_attachment_index=i, set=m, binding=n) uniform subpassInput myInputAttachment;

它映射到以下 SPIR-V

        ...
%1 = OpExtInstImport "GLSL.std.450"
        ...
        OpName %9 "myInputAttachment"
        OpDecorate %9 DescriptorSet m
        OpDecorate %9 Binding n
        OpDecorate %9 InputAttachmentIndex i
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeImage %6 SubpassData 0 0 0 2 Unknown
%8 = OpTypePointer UniformConstant %7
%9 = OpVariable %8 UniformConstant
        ...

input_attachment_index 为 i 选择输入通道列表中的第 i 个条目。(有关更多信息,请参阅 API 规范。)

这些对象支持通过以下函数读取子通道输入

gvec4 subpassLoad(gsubpassInput   subpass);
gvec4 subpassLoad(gsubpassInputMS subpass, int sample);

映射变量

gl_FragColor

片段阶段的内置变量 gl_FragColor 意味着广播到所有输出,它在 SPIR-V 中不存在。允许写入 gl_FragColor 的着色器仍然可以写入它,但这仅仅意味着写入与 gl_FragColor 类型相同的输出

  • gl_FragColor 类型相同

  • 使用位置 0 进行装饰

  • 未装饰为内置变量。

没有隐式广播。

Vulkan gl_VertexIndexgl_InstanceIndex

添加了两个新的内置变量 gl_VertexIndexgl_InstanceIndex 来替换现有的内置变量 gl_VertexIDgl_InstanceID

在索引相对于某个基本偏移量的情况下,对于 Vulkan,这些内置变量定义为具有以下值

gl_VertexIndex             base, base+1, base+2, ...
gl_InstanceIndex           base, base+1, base+2, ...

其中,基本值实际上是什么取决于具体情况。

存储类

uniform sampler2D...;        -> UniformConstant
uniform blockN { ... } ...;  -> Uniform, with Block decoration
in / out variable            -> Input/Output, possibly with block (below)
in / out block...            -> Input/Output, with Block decoration
buffer  blockN { ... } ...;  -> Uniform, with BufferBlock decoration
shared                       -> Workgroup
<normal global>              -> Private
Vulkan Only: buffer  blockN { ... } ...;  -> StorageBuffer, when requested
OpenGL Only: uniform variable (non-block) -> UniformConstant
OpenGL Only: ... uniform atomic_uint ...  -> AtomicCounter

输入/输出

输入/输出块或变量的映射对于所有版本的 GLSL 或 ESSL 都是相同的。在变量或成员在某个版本中可用的范围内,其位置如下

这些被映射到 SPIR-V 单独的变量,带有类似拼写的内置装饰(另有说明除外)

任何阶段

in gl_VertexIndex          (Vulkan only)
in gl_VertexID             (OpenGL only)
in gl_InstanceIndex        (Vulkan only)
in gl_InstanceID           (OpenGL only)
in gl_InvocationID
in gl_PatchVerticesIn      (PatchVertices)
in gl_PrimitiveIDIn        (PrimitiveID)
in/out gl_PrimitiveID      (in/out based only on storage qualifier)
in gl_TessCoord
in/out gl_Layer
in/out gl_ViewportIndex
patch in/out gl_TessLevelOuter  (uses Patch decoration)
patch in/out gl_TessLevelInner  (uses Patch decoration)

仅计算阶段

in gl_NumWorkGroups
in gl_WorkGroupSize
in gl_WorkGroupID
in gl_LocalInvocationID
in gl_GlobalInvocationID
in gl_LocalInvocationIndex

仅片段阶段

in gl_FragCoord
in gl_FrontFacing
in gl_ClipDistance
in gl_CullDistance
in gl_PointCoord
in gl_SampleID
in gl_SamplePosition
in gl_HelperInvocation
out gl_FragDepth
in gl_SampleMaskIn        (SampleMask)
out gl_SampleMask         (in/out based only on storage qualifier)

这些被映射到 SPIR-V 块,如伪代码所示,其成员用类似拼写的内置装饰进行装饰

非片段阶段

in/out gl_PerVertex {   // some subset of these members will be used
    gl_Position
    gl_PointSize
    gl_ClipDistance
    gl_CullDistance
}                       // name of block is for debug only

在 SPIR-V 中,每个阶段最多有一个输入和一个输出块。共享接口的阶段之间的成员子集和顺序将匹配。

仅 Vulkan:精度限定符的映射

lowp     -> RelaxedPrecision, on storage variable and operation
mediump  -> RelaxedPrecision, on storage variable and operation
highp    -> 32-bit, same as int or float
portability tool/mode  -> OpQuantizeToF16

precise 的映射

precise -> NoContraction

OpenGL atomic_uint offset 布局限定符的映射

offset         ->  Offset (decoration)

图像的映射

imageLoad()   -> OpImageRead
imageStore()  -> OpImageWrite
texelFetch()  -> OpImageFetch
subpassInput  -> OpTypeImage with Dim of SubpassData (Vulkan only)
subpassLoad() -> OpImageRead                         (Vulkan only)
imageAtomicXXX(params, data)  -> %ptr = OpImageTexelPointer params
                                        OpAtomicXXX %ptr, data
XXXQueryXXX(combined) -> %image = OpImage combined
                                OpXXXQueryXXX %image

布局的映射

std140/std430  ->  explicit Offset, ArrayStride, and MatrixStride
                    Decoration on struct members
shared/packed  ->  not allowed
<default>      ->  not shared, but std140 or std430
xfb_offset     ->  Offset Decoration on the object or struct member
xfb_buffer     ->  XfbBuffer Decoration on the object
xfb_stride     ->  XfbStride Decoration on the object
any xfb_*      ->  the Xfb Execution Mode is set
captured XFB   ->  has both XfbBuffer and Offset
non-captured   ->  lacking XfbBuffer or Offset
max_vertices   ->  OutputVertices

屏障的映射

barrier() (compute) -> OpControlBarrier(/*Execution*/Workgroup,
                                        /*Memory*/Workgroup,
                                        /*Semantics*/AcquireRelease |
                                                    WorkgroupMemory)
barrier() (tess control) -> OpControlBarrier(/*Execution*/Workgroup,
                                            /*Memory*/Invocation,
                                            /*Semantics*/None)
memoryBarrier() -> OpMemoryBarrier(/*Memory*/Device,
                                    /*Semantics*/AcquireRelease |
                                                UniformMemory |
                                                WorkgroupMemory |
                                                ImageMemory)
memoryBarrierBuffer() -> OpMemoryBarrier(/*Memory*/Device,
                                        /*Semantics*/AcquireRelease |
                                                    UniformMemory)
memoryBarrierShared() -> OpMemoryBarrier(/*Memory*/Device,
                                        /*Semantics*/AcquireRelease |
                                                    WorkgroupMemory)
memoryBarrierImage() -> OpMemoryBarrier(/*Memory*/Device,
                                        /*Semantics*/AcquireRelease |
                                                    ImageMemory)
groupMemoryBarrier() -> OpMemoryBarrier(/*Memory*/Workgroup,
                                        /*Semantics*/AcquireRelease |
                                                    UniformMemory |
                                                    WorkgroupMemory |
                                                    ImageMemory)

原子的映射

all atomic builtin functions -> Semantics = None(Relaxed)
atomicExchange()             -> OpAtomicExchange
imageAtomicExchange()        -> OpAtomicExchange
atomicCompSwap()             -> OpAtomicCompareExchange
imageAtomicCompSwap()        -> OpAtomicCompareExchange
N/A                          -> OpAtomicCompareExchangeWeak

仅 OpenGL:原子的映射

atomicCounterIncrement -> OpAtomicIIncrement
atomicCounterDecrement -> OpAtomicIDecrement
atomicCounter          -> OpAtomicLoad

其他指令的映射

%     -> OpUMod/OpSMod
mod() -> OpFMod
N/A   -> OpSRem/OpFRem
pack/unpack (conversion)    -> pack/unpack in GLSL extended instructions
pack/unpack (no conversion) -> OpBitcast