非规范性 SPIR‑V 映射
本附录包含内容:
- 对比 Vulkan 与 OpenGL 在使用 SPIR‑V 和不使用 SPIR‑V 时的功能差异
- 讨论 GLSL 功能如何逻辑映射到 SPIR‑V 功能
功能对比
以下功能在 OpenGL 和 Vulkan 中均被移除:
- 子程序(subroutines)
- shared 与 packed 块布局
- 已弃用的纹理采样函数(如
texture2D()) - 已弃用的噪声函数(如
noise1()) - 兼容模式(compatibility‑profile)特性
gl_DepthRangeParameters与gl_NumSamples
Vulkan 移除但 OpenGL 仍保留的功能:
- 非透明类型的默认 Uniform:可在全局作用域对单个变量使用
UniformConstant存储类(即 Uniform 不必放在块中,GLSL 4.5 及以上版本中属于块的内置成员除外) - GLSL 原子计数器绑定的
offset布局限定符 → SPIR‑VAtomicCounter存储类并使用Offset装饰 - GLSL
origin_lower_left→ SPIR‑VOriginLowerLeft - 顶点着色器中双精度输入的位置(location)特殊规则
gl_VertexID与gl_InstanceID(详见下文)
以下功能在 OpenGL 和 Vulkan 中均被添加:
- 专用常量(specialization constants)
offset可按与声明顺序不同的顺序组织成员- 对原本不支持的 GLSL 版本,为 Uniform / 缓冲块提供
offset与align布局限定符
仅 Vulkan 添加的功能:
- 推送常量缓冲区(push‑constant buffers)
- 着色器中独立纹理与采样器的组合(SPIR‑V
OpTypeSampler) - 描述符集(DescriptorSet,若存在则必须为 0)
gl_VertexIndex与gl_InstanceIndex- 子 pass 输入目标与输入附件(
input_attachment_index)
以下功能在 OpenGL 和 Vulkan 中均被修改:
gl_FragColor不再表示隐式广播
仅 Vulkan 修改的功能:
- 精度限定符(
mediump和lowp)在所有版本中均生效,桌面版不再忽略(桌面版所有类型默认精度为highp) - Uniform 数组与缓冲块数组对整个对象只占用一个绑定号,而非每个数组元素一个
- 默认坐标系原点为
origin_upper_left,而非origin_lower_left
Vulkan 的 SPIR‑V 环境规范不允许 UBO、SSBO 这类资源使用多维数组。SPIR‑V 本身支持该特性,且 OpenGL GLSL 着色器与面向 OpenGL 的 SPIR‑V 均允许使用。
GLSL 到 SPIR‑V 的映射
专用常量
SPIR‑V 专用常量可由上层 API 后续设置,在 GLSL 中通过 layout(constant_id=…) 声明。例如,声明一个默认值为 12 的专用常量:
layout(constant_id = 17) const int arraySize = 12;
其中 17 是 API 或工具后续引用该专用常量的 ID。在着色器最终编译为可执行代码前,API 或中间工具可修改其值;若未修改,则保留默认值 12。
专用常量具有 const 语义,但不会被常量折叠。因此可用于声明数组:
vec4 data[arraySize]; // 合法,即使 arraySize 可能被修改
专用常量可用于表达式:
vec4 data2[arraySize + 2];
data2 的大小将在着色器编译阶段由 arraySize 的最终值加 2 决定。
由专用常量构成的表达式在着色器中仍视为专用常量,而非普通常量:
arraySize + 2 // 专用常量(无 constant_id)
这类表达式可使用在普通常量允许的任何位置。
constant_id 仅可用于标量整型、标量浮点型或标量布尔型。
仅基础运算符与构造函数作用于专用常量后,结果仍为专用常量:
layout(constant_id = 17) const int arraySize = 12;
sin(float(arraySize)); // 结果不再是专用常量
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); // 部分专用化向量
内置变量可附加 constant_id:
layout(constant_id = 18) gl_MaxImageUnits;
这使其行为等同于专用常量,并非完整重声明;原内置声明的其他特性保持不变。
内置向量 gl_WorkGroupSize 可通过对 in 限定符使用专用 local_size_{xyz}_id 布局实现专用化:
layout(local_size_x_id = 18, local_size_z_id = 19) in;
gl_WorkGroupSize.y 仍为非专用常量,gl_WorkGroupSize 成为部分专用化向量,其 x、z 分量可后续通过 ID 18、19 专用化。
仅 Vulkan:推送常量
推送常量位于使用新布局限定符 push_constant 声明的 Uniform 块中。API 将一组常量写入推送常量缓冲区,着色器从 push_constant 块读取:
layout(push_constant) uniform BlockName {
int member1;
float member2;
...
} InstanceName; // 实例名可选
... = InstanceName.member2; // 读取推送常量
push_constant Uniform 块的内存管理与其他 Uniform 块不同:它必须适配一块独立的小容量内存池。默认情况下,推送常量缓冲区遵循 std430 打包规则。
仅 Vulkan:描述符集
描述符集中的每个着色器资源被分配一个三元组:(set 编号, binding 编号, 数组元素),用于定义其在描述符集布局中的位置。在 GLSL 中,set 编号与 binding 编号分别通过 set 与 binding 布局限定符指定;数组元素从 0 开始隐式连续分配(非数组变量为 0)。
// set=M, binding=N, array element=0
layout (set=M, binding=N) uniform sampler2D variableName;
// 所有数组元素共享 set=M, binding=N;第 i 个元素对应 array element=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 中使用 sampler 与 samplerShadow 类型声明:
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 中使用对应维度的组合类型声明:
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; // 仅过滤信息句柄
使用 texture2D 等声明的纹理仅包含图像信息,不包含过滤信息:
uniform texture2D t; // 纹理句柄(SPIR‑V 中为图像)
在纹理采样调用处可使用构造函数组合采样器与纹理:
texture(sampler2D(t, s), ...);
上述代码省略 layout() 以突出功能。
纹理缓冲(Uniform 纹素缓冲)
纹理缓冲在 GLSL 中使用 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 中使用 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
...
子 pass 输入
在渲染流程中,一个子 pass 可将结果写入输出目标,供后续子 pass 作为输入读取。子 pass 输入即读取这类输出目标的能力。
子 pass 输入通过一组仅片段着色器可用的新类型读取:
subpassInputsubpassInputMSisubpassInputisubpassInputMSusubpassInputusubpassInputMS
与采样器、图像对象不同,子 pass 输入通过片段的 (x, y, layer) 坐标隐式寻址。
输入附件除描述符集与绑定号外,还需使用 input_attachment_index 装饰:
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 规范)。
这类对象支持通过以下函数读取子 pass 输入:
gvec4 subpassLoad(gsubpassInput subpass);
gvec4 subpassLoad(gsubpassInputMS subpass, int sample);
变量映射
gl_FragColor
片段阶段内置变量 gl_FragColor 原本表示向所有输出广播,在 SPIR‑V 中不存在。允许写入 gl_FragColor 的着色器仍可写入,但仅表示写入满足以下条件的输出:
- 类型与
gl_FragColor相同 - 被
location = 0装饰 - 未被装饰为内置变量
无隐式广播行为。
Vulkan:gl_VertexIndex 与 gl_InstanceIndex
新增两个内置变量 gl_VertexIndex 与 gl_InstanceIndex,替换原有 gl_VertexID 与 gl_InstanceID。
在索引相对于某个基偏移的场景中,Vulkan 下其值定义如下:
gl_VertexIndex base, base+1, base+2, ...
gl_InstanceIndex base, base+1, base+2, ...
基值 base 由具体场景决定。
存储类映射
uniform sampler2D...; -> UniformConstant
uniform blockN { ... } ...; -> Uniform,带 Block 装饰
in / out variable -> Input/Output,可能带块(见下文)
in / out block... -> Input/Output,带 Block 装饰
buffer blockN { ... } ...; -> Uniform,带 BufferBlock 装饰
shared -> Workgroup
<普通全局变量> -> Private
- 仅 Vulkan:
buffer blockN { ... } ...;→ 按需映射为StorageBuffer - 仅 OpenGL:非块 Uniform 变量 →
UniformConstant - 仅 OpenGL:
... uniform atomic_uint ...→AtomicCounter
输入 / 输出映射
所有 GLSL/ESSL 版本中,输入 / 输出块或变量的映射规则一致。变量或成员在对应版本中可用时,其位置映射如下:
以下变量映射为 SPIR‑V 独立变量,并使用拼写相近的内置装饰(特别说明除外):
所有阶段:
in gl_VertexIndex (仅 Vulkan)
in gl_VertexID (仅 OpenGL)
in gl_InstanceIndex (仅 Vulkan)
in gl_InstanceID (仅 OpenGL)
in gl_InvocationID
in gl_PatchVerticesIn (PatchVertices)
in gl_PrimitiveIDIn (PrimitiveID)
in/out gl_PrimitiveID (仅由存储类决定输入/输出)
in gl_TessCoord
in/out gl_Layer
in/out gl_ViewportIndex
patch in/out gl_TessLevelOuter (使用 Patch 装饰)
patch in/out gl_TessLevelInner (使用 Patch 装饰)
仅计算阶段:
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 (仅由存储类决定输入/输出)
以下变量映射为 SPIR‑V 块(如伪代码所示),成员使用拼写相近的内置装饰:
非片段阶段:
in/out gl_PerVertex { // 仅使用成员子集
gl_Position
gl_PointSize
gl_ClipDistance
gl_CullDistance
} // 块名仅用于调试
SPIR‑V 中每个阶段最多一个输入块与一个输出块。共享接口的阶段之间,成员子集与顺序保持一致。
仅 Vulkan:精度限定符映射
lowp -> RelaxedPrecision,修饰存储变量与操作
mediump -> RelaxedPrecision,修饰存储变量与操作
highp -> 32‑bit,与 int/float 一致
可移植工具 / 模式 → OpQuantizeToF16
precise 映射 → NoContraction
仅 OpenGL:atomic_uint offset 布局限定符映射
offset -> Offset 装饰
图像操作映射
imageLoad() -> OpImageRead
imageStore() -> OpImageWrite
texelFetch() -> OpImageFetch
subpassInput -> OpTypeImage,维度为 SubpassData(仅 Vulkan)
subpassLoad() -> OpImageRead(仅 Vulkan)
imageAtomicXXX(params, data) -> %ptr = OpImageTexelPointer params
OpAtomicXXX %ptr, data
XXXQueryXXX(combined) -> %image = OpImage combined
OpXXXQueryXXX %image
布局映射
std140/std430 -> 结构体成员使用显式 Offset、ArrayStride、MatrixStride 装饰
shared/packed -> 不允许
<default> -> 非 shared,使用 std140 或 std430
xfb_offset -> 对象或结构体成员使用 Offset 装饰
xfb_buffer -> 对象使用 XfbBuffer 装饰
xfb_stride -> 对象使用 XfbStride 装饰
any xfb_* -> 设置 Xfb Execution Mode
captured XFB -> 同时拥有 XfbBuffer 与 Offset
non-captured -> 缺少 XfbBuffer 或 Offset
max_vertices -> OutputVertices
屏障映射
// 计算着色器 barrier()
barrier() -> OpControlBarrier(/*Execution*/Workgroup,
/*Memory*/Workgroup,
/*Semantics*/AcquireRelease |
WorkgroupMemory)
// 曲面细分控制着色器 barrier()
barrier() -> 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)
原子操作映射
所有原子内置函数 → 语义 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(类型转换) -> GLSL 扩展指令中的 pack/unpack
pack/unpack(无类型转换) -> OpBitcast
767

被折叠的 条评论
为什么被折叠?



