当实现从接口访问内存时,需要知晓对应的内存布局,其中包括偏移量、步长和对齐方式等信息。尽管 Vulkan 规范中设有专门章节讲解这一内容,但因各类扩展为规范描述增加了额外复杂度,相关内容难以理解。本章将结合高级着色语言(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风格的布局。
当应用程序希望统一缓冲区的数组步长不再受限于扩展对齐时,就需要启用uniformBufferStandardLayout特性,以下为典型示例(可在线尝试)
layout(std140, binding = 0) uniform ubo140 {
float array140[8];
};
layout(std430, binding = 1) uniform ubo430 {
float array430[8];
};
其对应的 SPIR-V 代码为
// 数组的扩展对齐向上取整为16的倍数
OpDecorate %array140 ArrayStride 16
// 基础对齐为4字节(OpTypeFloat 32)
// 仅在启用uniformBufferStandardLayout特性时生效
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 字节对齐,而启用松弛块布局后,应用程序可在vec3两侧放置float类型变量,且能保证二者之间保持 16 字节对齐。
// 未启用松弛块布局的SPIR-V偏移量
layout (set = 0, binding = 0) buffer block {
float b; // 偏移量:0
vec3 a; // 偏移量:16
} ssbo;
// 启用松弛块布局的SPIR-V偏移量
layout (set = 0, binding = 0) buffer block {
float b; // 偏移量:0
vec3 a; // 偏移量: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
此扩展允许大多数存储类型采用标量对齐方式,其中一个重要特性是支持跨 16 字节边界的内存布局。
在 GLSL 中,可通过scalar关键字并启用对应扩展来使用该特性:
#extension GL_EXT_scalar_block_layout : enable
layout (scalar, binding = 0) buffer block { }
运行 SPIR-V 验证器时,需确保设置了--scalar-block-layout参数。
VK_EXT_scalar_block_layout不支持Workgroup存储类,若要为该存储类启用标量对齐支持,需使用 VK_KHR_workgroup_memory_explicit_layout 中的workgroupMemoryExplicitLayoutScalarBlockLayout特性。
对齐示例
以下为多个 GLSL 转 SPIR-V 的示例,帮助更好地理解不同对齐方式的差异。
对齐示例 1
layout(binding = 0) buffer block {
vec2 a[4];
vec4 b;
};
其对应的 SPIR-V 代码为
// 扩展对齐(std140)
OpDecorate %vec2array ArrayStride 16
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 64
// 标量对齐与基础对齐(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 代码为
// 扩展对齐(std140)与基础对齐(std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 8
OpMemberDecorate %block 2 Offset 16
// 标量对齐
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 代码为
// 扩展对齐(std140)与基础对齐(std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 16
OpMemberDecorate %block 2 Offset 32
// 标量对齐
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 代码为
// 扩展对齐(std140)与基础对齐(std430)
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 16
OpMemberDecorate %block 2 Offset 24
OpMemberDecorate %block 3 Offset 32
// 标量对齐
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %block 1 Offset 12
OpMemberDecorate %block 2 Offset 20
OpMemberDecorate %block 3 Offset 28

386

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



