Shader内存布局
当驱动实现访问 memory 时,它需要知道 memory 的布局是什么,包括偏移、步长和对齐等信息。 Vulkan Spec有一个章节专门介绍这块内容,由于增加了各种扩展,因此有的内容可能会有些复杂。本章旨在通过一些高级着色语言 (GLSL) 示例来帮助理解 Vulkan 使用的内存布局概念。
对齐要求
Vulkan 有 3 个对齐要求,接口对象可以按照这些要求进行布局:
- 扩展对齐(也称为
std140
) - 基础对齐(也称为
std430
) - 标量对齐
对齐规范可细分成以下每种块类型的规则:
- 标量 (
float
、int
、char
等) - 向量(
float2
、vec3
、uvec4
等) - 矩阵
- 数组
- 结构
VK_KHR_uniform_buffer_standard_layout
在 Vulkan 1.2 中提升为核心功扩展。
此扩展允许 UBO 使用std430
内存布局,更详细的内容可阅读Vulkan 标准缓冲区布局接口。这些内存布局的变动仅对Uniforms
生效,其他存储项(如 Push Constants
和 SSBO
)已经允许使用 std430
布局样式。
应用程序如果不希望 UBO 数组步长限制为extended alignment
,则可以使用uniformBufferStandardLayout
功能:
layout(std140, binding = 0) uniform ubo140 {
float array140[8];
};
layout(std430, binding = 1) uniform ubo430 {
float array430[8];
};
SPIR-V 转换为:
// extended alignment for array is rounded up to multiple of 16
OpDecorate %array140 ArrayStride 16
// base alignment is 4 bytes (OpTypeFloat 32)
// only valid with uniformBufferStandardLayout feature enabled
OpDecorate %array430 ArrayStride 4
请确保在运行 SPIR-V 验证器时设置--uniform-buffer-standard-layout
。
VK_KHR_relaxed_block_layout
此扩展在 Vulkan 1.1 中提升为核心扩展,该扩展未添加任何功能位,因此所有 Vulkan 1.1+ 设备都支持宽松的块布局。
此扩展允许驱动实现支持更多块偏移(Offset
)的修饰方式。当使用std430
内存布局时,vec3
(12 字节) 仍使用16 字节对齐。使用宽松的块布局,应用程序可以对float
和vec3
使用更合适的对齐长度:
// SPIR-V offsets WITHOUT relaxed block layout
layout (set = 0, binding = 0) buffer block {
float b; // Offset: 0
vec3 a; // Offset: 16
} ssbo;
// SPIR-V offsets WITH relaxed block layout
layout (set = 0, binding = 0) buffer block {
float b; // Offset: 0
vec3 a; // Offset: 4
} ssbo;
VK_KHR_relaxed_block_layout
可以看作是VK_EXT_scalar_block_layout
的子集。
确保在 Vulkan 1.0 环境运行 SPIR-V 验证器时设置--relax-block-layout
。
目前GLSL 规范还没有明确地表达宽松的块布局细节,开发人员可以使用glslang
的--hlsl-offsets
参数来产生所需的偏移量。
VK_EXT_scalar_block_layout
在 Vulkan 1.2 中提升为核心扩展GLSL - GL_EXT_scalar_block_layout。
此扩展允许大多数存储类型按scalar alignment
对齐,一个很大的区别是能够跨越 16 字节的边界。
在 GLSL 中,这可以与scalar
关键字一起使用:
#extension GL_EXT_scalar_block_layout : enable
layout (scalar, binding = 0) buffer block { }
确保在运行 SPIR-V 验证器时设置--scalar-block-layout
。
Workgroup
存储类不支持VK_EXT_scalar_block_layout
,支持scalar
功能需要在VK_KHR_workgroup_memory_explicit_layout 中设置workgroupMemoryExplicitLayoutScalarBlockLayout
。
对齐的示例
以下是一些 GLSL 到 SPIR-V 示例,用来帮助理解对齐的差异。
对齐示例 1
layout(binding = 0) buffer block {
vec2 a[4];
vec4 b;
};
在 SPIR-V 中转换为:
// extended alignment (std140)
OpDecorate %vec2array ArrayStride 16
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 64
// scalar alignment and base alignment (std430)
OpDecorate %vec2array ArrayStride 8
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 32
示例 2
layout(binding = 0) buffer block {
float a;
vec2 b;
vec2 c;
};
在 SPIR-V 中转换为:
// extended alignment (std140) and base alignment (std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 8
OpMemberDecorate %block 2 Offset 16
// scalar alignment
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 4
OpMemberDecorate %block 2 Offset 12
示例 3
layout(binding = 0) buffer block {
vec3 a;
vec2 b;
vec4 c;
};
在 SPIR-V 中转换为:
// extended alignment (std140) and base alignment (std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 16
OpMemberDecorate %block 2 Offset 32
// scalar alignment
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 12
OpMemberDecorate %block 2 Offset 20
示例 4
layout (binding = 0) buffer block {
vec3 a;
vec2 b;
vec2 c;
vec3 d;
};
在 SPIR-V 中转换为:
// extended alignment (std140) and base alignment (std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 16
OpMemberDecorate %block 2 Offset 24
OpMemberDecorate %block 3 Offset 32
// scalar alignment
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 12
OpMemberDecorate %block 2 Offset 20
OpMemberDecorate %block 3 Offset 28