Overview
Vulkan
中的缓存资源通过VkBuffer
对象来表示,是一种用于存储通用数据的资源,可以用来存储顶点数据,索引数据,Uniforms
数据块等
VkBuffer
表示的是一个线性内存块,这说明它的内存布局是连续的,类似于数组,这种布局适合存储顺序访问的数据,比如 顶点数据和索引数据,也支持随机访问
VkBuffer 创建
创建 VkBuffer
时,可以通过设置 VkBufferCreateInfo
不同的 usage
标志来指定 VkBuffer
的用途
vkCreateBuffer
的函数原型是:
VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(
VkDevice device,
const VkBufferCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkBuffer* pBuffer);
其中的关键配置信息是pCreateInfo
,其类型VkBufferCreateInfo
定义如下:
typedef struct VkBufferCreateInfo {
VkStructureType sType; // 必须为 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
const void* pNext; // nullptr
VkBufferCreateFlags flags; // 0
VkDeviceSize size; // 缓存大小,单位为字节
VkBufferUsageFlags usage; // usage ,important
VkSharingMode sharingMode; // 缓存的共享模式
uint32_t queueFamilyIndexCount; // 指定 pQueueFamilyIndices 数组中元素数量
const uint32_t* pQueueFamilyIndices;// 指定将会访问该缓存的设备队列
} VkBufferCreateInfo;
其中需要重点关注的是 VkBufferUsageFlags
,定义如下:
typedef enum VkBufferUsageFlagBits {
VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,
VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100,
}
VK_BUFFER_USAGE_TRANSFER_SRC_BIT
该缓存用于数据传输的数据源VK_BUFFER_USAGE_TRANSFER_DST_BIT
该缓存用于数据传输的目的数据VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
该缓存用于传输统一纹理像素VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT
该缓存用于保存纹理像素数据VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
该缓存用于传输任意统一数据VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
该缓存用于存储任意格式数据。用于设备读取和存VK_BUFFER_USAGE_INDEX_BUFFER_BIT
该缓存用于存储整型索引数据VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
该缓存用于存储具有相同结构的顶点数据VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT
该缓存用于间接数据。用于存储指令参数,设备可一次性读取这些参数
资源共享类型 vkSharingMode
typedef enum VkSharingMode {
VK_SHARING_MODE_EXCLUSIVE = 0,
VK_SHARING_MODE_CONCURRENT = 1,
VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF
} VkSharingMode;
-
VK_SHARING_MODE_EXCLUSIVE
设备队列独享资源,该资源一次只能被一种设备队列族中的队列访问 -
VK_SHARING_MODE_CONCURRENT
设备队列共享资源,该资源只能一次被多种设备队列族中的队列访问
需要注意的是,在Vulkan
中创建的所有资源(VkBuffer
, VkImage
等)都是虚资源
也就是说,创建的资源只是占用了一个位置,创建了一个句柄,并没有对应存储资源数据的内存,后续要通过 vkBindBufferMemory
将资源绑定到相应的设备内存 VkDeviceMemory
,所以数据实际存储在设备内存
一旦设备内存绑定到一个资源对象(VkBuffer
, VkImage
)上,这个内存绑定就不能再次改变了
VkBufferRequirement 查询
在设备内存绑定到资源上之前,需要确定使用什么类型的内存,以及资源需要多少内存
这个时候,可以使用vkGetBufferMemoryRequirements
获取缓冲区的内存需求,包括内存大小,对齐要求以及适合的内存类型,注意此API 是查询已经创建的 VkBuffer
的信息
vkGetBufferMemoryRequirements
的函数原型:
VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(
VkDevice device,
VkBuffer buffer,
VkMemoryRequirements* pMemoryRequirements);
其中的 VkMemoryRequirements
包含了缓冲区的内存需求信息:
typedef struct VkMemoryRequirements {
VkDeviceSize size; // 缓冲区所需的内存大小(以自己为单位)
VkDeviceSize alignment; // 缓冲区的内存对齐要求
uint32_t memoryTypeBits; // 缓冲区的可用内存类型掩码和 枚举内存信息的flag 匹配
} VkMemoryRequirements;
参考代码实现如下:
uint32_t vulkanBasicDemo::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("failed to find suitable memory type!");
}
void vulkanBasicDemo::vulkanCreateVulkanBuffer() {
// 顶点数据的vector
std::vector<float> vertices = {
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
};
// 创建 VkBuffer 用于存储顶点数据
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.pNext = nullptr;
bufferInfo.flags = 0;
bufferInfo.size = sizeof(vertices); // 设置缓冲区大小
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // 设置缓冲区用途 USAGE_VERTEX_BUFFER
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // 共享模式
bufferInfo.queueFamilyIndexCount = 1;
bufferInfo.pQueueFamilyIndices = &graphicsQueueFamilyIndex;//设备队列索引
VkBuffer vertexBuffer;
if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to create vertex buffer!");
}
std::cout << "Create VkBuffer Success!!" << std::endl;
// 获取内存需求, 包含 Vulkan 内存对齐信息,以及内存对齐之后,内存的 size\memoryTypeBits
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
// 分配设备内存
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
// 获取到对CPU可见且自动同步的设备内存类型
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VkDeviceMemory vertexBufferMemory;
if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate vertex buffer memory!");
}
// 绑定内存
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
std::cout << "VkBuffer Bind Memory Success!!" << std::endl;
// 内存映射,填充数据
void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);//获取设备内存映射的内存地址
memcpy(data, vertices.data(), (size_t)bufferInfo.size);//将顶点数据拷贝到设备内存映射的内存地址
// 内存解映射
vkUnmapMemory(device, vertexBufferMemory);
}