Part 2. Vulkan中的顶点缓冲和索引缓冲
转载自最早发布在formu.ziyuesinicization.site的文章
首先,我们需要写顶点数据和索引数据:
typedef struct Vertex
{
float position[3];
float color[3];
} Vertex;
Vertex vertices[4] = {
{{-0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}},
{{ 0.5f, 0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}},
{{ 0.5f, -0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}},
{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}}
};
unsigned int indices[6] = {
0, 2, 1,
0, 3, 2
};
接着编写着色器
IO.hlsl
#ifndef IO_HLSL
#define IO_HLSL
struct VS_INPUT
{
float3 position : POSITION0;
float3 color : COLOR0;
};
struct VS_OUTPUT
{
float4 position : SV_POSITION;
float3 color : COLOR0;
};
#endif
vertex.hlsl
#include "IO.hlsl"
VS_OUTPUT main(VS_INPUT input, uint vertexID : SV_VertexID)
{
VS_OUTPUT output;
output.position = float4(input.position, 1.0);
output.color = input.color;
return output;
}
pixel.hlsl
#include "IO.hlsl"
float4 main(VS_OUTPUT input) : SV_Target
{
return float4(input.color, 1.0);
}
有了着色器之后,我们就可以去搞CPU端的事情了。
首先,我们需要先了解Vulkan中,一个缓冲是如何被创建和使用的。
我们需要先声明一个缓冲,就像声明一个变量一样:
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
vkCreateBuffer(device, &bufferInfo, nullptr, buffer);
然后我们需要为创建的缓冲分配一个内存,为了找到一个合适的内存,我们需要借助一个函数:
uint32_t FindMemoryType(VkPhysicalDevice physicalDevice, 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("No suitable memory type found!");
}
之后我们就可以为缓冲分配内存了:
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, *buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);
vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory);
vkBindBufferMemory(device, *buffer, *bufferMemory, 0);
由于后面要大量用到这个操作,我们把刚刚的操作整理为一个函数:
void CreateAndAllocateBuffer(VkDevice device,VkPhysicalDevice physicalDevice,
VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
VkBuffer* buffer, VkDeviceMemory* bufferMemory)
{
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
vkCreateBuffer(device, &bufferInfo, nullptr, buffer);
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, *buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);
vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory);
vkBindBufferMemory(device, *buffer, *bufferMemory, 0);
}
在渲染管线之前,我们开始创建缓冲,并且把数据复制到缓冲然后上传到GPU
为了把数据填充到缓冲区,直接映射内存时,如果内存类型为VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, 则需要vkFlushMappedMemoryRanges 来确保数据可见,但是我们使用一个性能更高的办法,用暂存缓冲区的办法,去填充。
先计算先前数据的大小:
VkDeviceSize verticesSize = 4 * sizeof(Vertex);
VkDeviceSize indicesSize = 6 * sizeof(unsigned int);
接着声明暂存缓冲区:
VkBuffer tempBuffer;
VkDeviceMemory tempBufferMemory;
我们先为顶点缓冲区填充数据,这一步需要建立一个临时的命令缓冲区,然后提交到GPU:
先把数据填充到暂存缓冲区
CreateAndAllocateBuffer(device,physicalDevice,
verticesSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&tempBuffer, &tempBufferMemory);
void* data;
vkMapMemory(device, tempBufferMemory, 0, verticesSize, 0, &data);
memcpy(data, vertices, (size_t)verticesSize);
vkUnmapMemory(device, tempBufferMemory);
创建顶点缓冲区:
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
//Vertex Buffer
CreateAndAllocateBuffer(device,physicalDevice,
verticesSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&vertexBuffer, &vertexBufferMemory);
然后建立临时命令缓冲区,并提交复制命令:
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer tempCommandBuffer;
vkAllocateCommandBuffers(device, &allocInfo, &tempCommandBuffer);
VkCommandBufferBeginInfo tempBeginInfo{};
tempBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
tempBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(tempCommandBuffer, &tempBeginInfo);
VkBufferCopy copyRegion{};
copyRegion.size = verticesSize;
vkCmdCopyBuffer(tempCommandBuffer, tempBuffer, vertexBuffer, 1, ©Region);
//End Copy Command
vkEndCommandBuffer(tempCommandBuffer);
VkSubmitInfo tempSubmitInfo{};
tempSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
tempSubmitInfo.commandBufferCount = 1;
tempSubmitInfo.pCommandBuffers = &tempCommandBuffer;
VkFence tempFence;
VkFenceCreateInfo tempFenceInfo{};
tempFenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(device, &tempFenceInfo, nullptr, &tempFence);
vkQueueSubmit(graphicsQueue, 1, &tempSubmitInfo, tempFence);
vkWaitForFences(device, 1, &tempFence, VK_TRUE, UINT64_MAX);
别忘了销毁刚刚创建的暂存缓冲区和临时命令缓冲区:
vkDestroyFence(device, tempFence, nullptr);
vkFreeCommandBuffers(device, commandPool, 1, &tempCommandBuffer);
vkDestroyBuffer(device, tempBuffer, nullptr);
vkFreeMemory(device, tempBufferMemory, nullptr);
索引缓冲同样的操作:
CreateAndAllocateBuffer(device,physicalDevice,
indicesSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&tempBuffer, &tempBufferMemory);
vkMapMemory(device, tempBufferMemory, 0, indicesSize, 0, &data);
memcpy(data, indices, (size_t)indicesSize);
vkUnmapMemory(device, tempBufferMemory);
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;
//Index Buffer
CreateAndAllocateBuffer(device,physicalDevice,
indicesSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&indexBuffer, &indexBufferMemory);
//Begin Copy Command
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
vkAllocateCommandBuffers(device, &allocInfo, &tempCommandBuffer);
tempBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
tempBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(tempCommandBuffer, &tempBeginInfo);
copyRegion.size = indicesSize;
vkCmdCopyBuffer(tempCommandBuffer, tempBuffer, indexBuffer, 1, ©Region);
//End Copy Command
vkEndCommandBuffer(tempCommandBuffer);
tempSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
tempSubmitInfo.commandBufferCount = 1;
tempSubmitInfo.pCommandBuffers = &tempCommandBuffer;
tempFenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(device, &tempFenceInfo, nullptr, &tempFence);
vkQueueSubmit(graphicsQueue, 1, &tempSubmitInfo, tempFence);
vkWaitForFences(device, 1, &tempFence, VK_TRUE, UINT64_MAX);
vkDestroyFence(device, tempFence, nullptr);
vkFreeCommandBuffers(device, commandPool, 1, &tempCommandBuffer);
vkDestroyBuffer(device, tempBuffer, nullptr);
vkFreeMemory(device, tempBufferMemory, nullptr);
接着,我们修改vertexInputCreateInfo,根据一开始的数据,我们顶点布局为,一个位置向量和一个颜色向量,我们要填写VkVertexInputBindingDescription和VkVertexInputAttributeDescription,示例如下:
VkVertexInputBindingDescription bindingDesc{};
bindingDesc.binding = 0;
bindingDesc.stride = sizeof(Vertex);
bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attrDescs[2];
attrDescs[0].binding = 0;
attrDescs[0].location = 0;
attrDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attrDescs[0].offset = offsetof(Vertex, position);
attrDescs[1].binding = 0;
attrDescs[1].location = 1;
attrDescs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attrDescs[1].offset = offsetof(Vertex, color);
接着,修改顶点布局描述:
vertexInputCreateInfo.vertexBindingDescriptionCount = 1;
vertexInputCreateInfo.pVertexBindingDescriptions = &bindingDesc;
vertexInputCreateInfo.vertexAttributeDescriptionCount = 2;
vertexInputCreateInfo.pVertexAttributeDescriptions = attrDescs;
我们来到循环,分别绑定顶点缓冲和索引缓冲,然后调用绘制命令即可:
VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = { 0 };
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(commandBuffer, 6, 1, 0, 0, 0);