跳到主要内容

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 是无法实现的。


🚀 使用步骤

  1. 开启扩展:
cpp复制编辑enabledExtensions = {
"VK_KHR_buffer_device_address"
};
  1. 启用设备特性:
cpp复制编辑VkPhysicalDeviceBufferDeviceAddressFeatures features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
.bufferDeviceAddress = VK_TRUE
};
  1. 创建 buffer 时加 flag:
cpp


复制编辑
bufferCreateInfo.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
  1. 调用 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 APIvkGetBufferDeviceAddress

示例

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。它直接代表链表起始节点。

✅ 总结步骤

  1. 启用 VK_KHR_buffer_device_address
  2. 创建 buffer + device 地址 buffer memory
  3. 调用 vkGetBufferDeviceAddress
  4. 构造链表,写入 GPU 地址
  5. 在 GLSL shader 中使用 buffer_reference 指向链表 start
  6. 在 shader 中自由跳转、读取结构数据