KHR_buffer_device_address的用法
· 阅读需 5 分钟
VK_KHR_buffer_device_address
扩展是 Vulkan 中非常重要的一个功能,它允许你从 GPU 上直接获取 Buffer 的“设备地址”,从而支持:
✅ 指针式 GPU 编程(如 GPU 上的结构体链表、指针跳转等)
🧩 简单理解:它的作用是什么?
传统 Vulkan 中,GPU 无法知道 buffer 在设备上的地址,只能通过 binding 来访问。但开启 VK_KHR_buffer_device_address
后:
你可以在 GPU shader 中使用设备地址 (
deviceAddress
) 指向某个 Buffer 的任意位置,像指针一样访问内存。
✅ 主要作用和用途
作用 | 示例用途 |
---|---|
1️⃣ 获取 GPU 上的 buffer 地址 | vkGetBufferDeviceAddress 取得指针 |
2️⃣ Shader 中可以操作 raw 设备地址 | 用于构造链表、树、BVH 等 |
3️⃣ 启用 GPU pointer 计算 | 类似 C++ 指针,在 Shader 里操作内存 |
4️⃣ 实现自定义内存管理器 | 动态分配、跳转、遍历 GPU 数据结构 |
5️⃣ 支持 RTX 和加速结构(如 BLAS/TLAS) | 必须要求 device address |
🧪 示例:获取 Buffer 的设备地址
cpp复制编辑VkBufferDeviceAddressInfo addr_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = myBuffer
};
VkDeviceAddress addr = vkGetBufferDeviceAddress(device, &addr_info);
这个地址可以传入 Shader,通过 uint64_t
接收,然后做指针跳转或访问。
✍️ GLSL 中怎么使用?
glsl复制编辑#extension GL_EXT_buffer_reference : enable
#extension GL_EXT_buffer_reference2 : enable
#extension GL_EXT_scalar_block_layout : enable
layout(buffer_reference, scalar) buffer Node;
struct Node {
float value;
Node next; // 嵌套引用
};
layout(set = 0, binding = 0) buffer Root {
Node root;
};
void main() {
Node current = root;
while (current.value < 10.0) {
current = current.next;
}
}
这在传统 Vulkan 是无法实现的。
🚀 使用步骤
- 开启扩展:
cpp复制编辑enabledExtensions = {
"VK_KHR_buffer_device_address"
};
- 启用设备特性:
cpp复制编辑VkPhysicalDeviceBufferDeviceAddressFeatures features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
.bufferDeviceAddress = VK_TRUE
};
- 创建 buffer 时加 flag:
cpp
复制编辑
bufferCreateInfo.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
- 调用
vkGetBufferDeviceAddress()
获取地址
⚠️ 注意事项
限制 | 描述 |
---|---|
必须 GPU 驱动支持该扩展 | |
必须设置 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | |
仅支持 Vulkan 1.2+ 或通过扩展开启 | |
使用 device address 意味着需要注意对齐、访问合法性(你在 shader 中“越界”会引发 crash) |
✅ 总结
项目 | 说明 |
---|---|
扩展名 | VK_KHR_buffer_device_address |
作用 | 获取 Buffer 的 GPU 设备地址 |
用途 | GPU 指针访问、加速结构(如 RTX)、GPU 内存池等 |
Shader 访问 | GLSL/GL_EXT_buffer_reference + 指针跳转 |
Vulkan API | vkGetBufferDeviceAddress |
示例
1️⃣ Vulkan 端准备
启用扩展 VK_KHR_buffer_device_address
并设置特性
创建 buffer 时加上 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
分配内存 时加上 VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT
读取 GPU 地址 使用 vkGetBufferDeviceAddress
// 1. 在 VkDeviceCreateInfo 的 pNext 链中启用特性
VkPhysicalDeviceBufferDeviceAddressFeaturesKHR bda_feat{};
bda_feat.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR;
bda_feat.bufferDeviceAddress = VK_TRUE;
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES2};
features2.pNext = &bda_feat;
vkGetPhysicalDeviceFeatures2(physDevice, &features2);
VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
di.pNext = &features2;
vkCreateDevice(physDevice, &di, nullptr, &device);
// 2. 创建 buffer 并分配支持 device address 的 memory
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bi.size = sizeof(Node) * MAX_NODES;
bi.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
VkBuffer buf;
vkCreateBuffer(device, &bi, nullptr, &buf);
VkMemoryRequirements mr;
vkGetBufferMemoryRequirements(device, buf, &mr);
VkMemoryAllocateFlagsInfo flagsInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO};
flagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
mai.allocationSize = mr.size;
mai.memoryTypeIndex = find_mem_type(mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
mai.pNext = &flagsInfo;
VkDeviceMemory mem;
vkAllocateMemory(device, &mai, nullptr, &mem);
vkBindBufferMemory(device, buf, mem, 0);
// 3. 获取 GPU 地址
VkBufferDeviceAddressInfoKHR addrInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
addrInfo.buffer = buf;
uint64_t gpuAddr = vkGetBufferDeviceAddressKHR(device, &addrInfo);
2️⃣ CPU 端构造链表数据
struct Node { uint32_t data; uint64_t next; };
std::vector<Node> nodes = {
{10, gpuAddr + sizeof(Node)*1},
{20, gpuAddr + sizeof(Node)*2},
{30, 0ull}
};
vkMemcpyToBuffer(buf, nodes.data(), sizeof(Node) * nodes.size());
这里 next
存储了 GPU 地址,指向下一个节点形成链表。
3️⃣ GLSL Shader 中使用指针访问
需要开启扩展和 buffer reference:
#version 450
#extension GL_EXT_buffer_reference : require
layout(buffer_reference, buffer_reference_align=8) buffer Node {
uint data;
Node next;
};
layout(std430, set=0, binding=0) buffer Root {
Node root;
};
layout(set=0, binding=1) uniform UBO {
uint dummy; // 可选
} ubo;
layout(location=0) out uint outData;
void main() {
Node cur = root;
while (cur.next != 0u) {
cur = cur.next;
}
outData = cur.data; // 应该输出 30
}
在 shader 中,root
从 descriptor set 0 绑定,并包含 gpuAddr
。它直接代表链表起始节点。
✅ 总结步骤
- 启用
VK_KHR_buffer_device_address
- 创建 buffer + device 地址 buffer memory
- 调用
vkGetBufferDeviceAddress
- 构造链表,写入 GPU 地址
- 在 GLSL shader 中使用
buffer_reference
指向链表 start - 在 shader 中自由跳转、读取结构数据