vulkan代码按照这个教程的基础上进行拓展
https://vulkan-tutorial.com/
参考了知乎大佬的笔记
和github大佬的代码
需要将vulkan与游戏引擎向结合,最基础的就是要把每一个game object画出来
基础教程中只画了一个图形,而vulkan的draw又是用command buffer预先录制,所以不能采用OpenGL那种更新一次uniform再draw一次的方法,效率也不高。
Vulkan可以用dynamic uniform来做这件事情。
1.申请uniform buffer
定义Dynamic Uniform Buffer Object
struct DynamicUBO {
glm::mat4 *model = nullptr;
};
新增一组空指针来保存地址, 并申明dynamic ubo变量,以及2个对齐变量,等等
std::vector<void*> dynamicUniformData;
size_t dynamicAlignment;
size_t normalUBOAlignment;
DynamicUBO uboDynamic;
vulkan 内存管理,最好申请一块buffer,用offset的形式来使用,但是Vertex, Index buffer和uniform buffer的usage和property flag不尽相同,而且按照上文vulkan的教程,uniform buffer要有多块与swapchain数量对应,所以这里为了方便就另外申请了
dynamic和普通的uniform buffer还是可以放一起的,用minUboAlignment计算出最小间隔,从而得出offset的值。申请完Buffer后马上将指针map上去。
这里的VK_MEMORY_PROPERTY_HOST_COHERENT_BIT加不加在我电脑上看不出区别,可能和显卡型号有关,下面的flush也是,因为就在我电脑上搞搞就先不纠结这些了。
//dynamic
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
size_t minUboAlignment = properties.limits.minUniformBufferOffsetAlignment;
dynamicAlignment = sizeof(glm::mat4);
if (minUboAlignment > 0) {
dynamicAlignment = (dynamicAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
}
VkDeviceSize bufferSize = LONG_SIZE * dynamicAlignment;
uboDynamic.model = (glm::mat4*)alignedAlloc(bufferSize, dynamicAlignment);
dynamicUniformData.resize(swapChainImages.size());
//normal
normalUBOAlignment = sizeof(UniformBufferObject);
if (minUboAlignment > 0) {
normalUBOAlignment = (normalUBOAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
}
bufferSize += normalUBOAlignment;
uniformBuffers.resize(swapChainImages.size());
uniformBuffersMemory.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); ++i)
{
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);
vkMapMemory(device, uniformBuffersMemory[i], 0, LONG_SIZE * dynamicAlignment, 0, &dynamicUniformData[i]);
}
shader里把model矩阵单独拿出来作为binding 1
layout(binding = 1) uniform UboInstance{
mat4 model;
}uboInstance;
2.下面是descriptor 相关的一套操作。
Vulkan Shader Resource Binding
在DescriptorSetLayout里新增一个dyanmic的binding
VkDescriptorSetLayoutBinding uboDynamicLayoutBinding = {};
uboDynamicLayoutBinding.binding = 1;
uboDynamicLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
uboDynamicLayoutBinding.descriptorCount = 1;
uboDynamicLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
uboDynamicLayoutBinding.pImmutableSamplers = nullptr;
3.修改DescriptorPool
poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
poolSizes[1].descriptorCount = static_cast<uint32_t>(swapChainImages.size());
4.在DescriptorSet里新增一个VkWriteDescriptorSet
别忘了描述下在buffer里的位置
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = normalUBOAlignment;
VkDescriptorBufferInfo dynamicBufferInfo = {};
dynamicBufferInfo.buffer = uniformBuffers[i];
dynamicBufferInfo.offset = normalUBOAlignment;
dynamicBufferInfo.range = dynamicAlignment;
加一个write
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pBufferInfo = &dynamicBufferInfo;
descriptorWrites[1].pTexelBufferView = nullptr;
5.画之前更新数据
先更新视距矩阵的信息
void VulkanAPI::updateUniformBuffer(uint32_t currentImage)
{
UniformBufferObject ubo = {};
glm::vec4 view_dir = *cameraTransform * glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
glm::vec4 view_up = *cameraTransform * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);
glm::vec4 view_pos = *cameraTransform * glm::vec4(cameraOffset, 1.0f);
ubo.view = glm::lookAt(glm::vec3(view_pos), glm::vec3(view_pos + view_dir), glm::vec3(view_up));
ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f);
ubo.proj[1][1] *= -1;
memcpy(dynamicUniformData[currentImage], &ubo, sizeof(UniformBufferObject));
}
用dynamic uniform更新transform,注意偏移量指针需要强制转换成size_t来对应偏移单位
(此处可以优化,transform里的matrix作为指针指向这个medelMat,增删物体的时候做对应的指针变换,即可节省下这个复制的过程)
void VulkanAPI::updateDynamicUniformBuffer(uint32_t currentImage)
{
uint32_t index = 0;
for (auto transID: ComponentManager::GetInstance()->activeComponents[TRANSFORM])
{
glm::mat4* modelMat = (glm::mat4*)(((uint64_t)uboDynamic.model + (index * dynamicAlignment)));
*modelMat = ComponentManager::GetInstance()->mTransforms[transID].transMatrix;
++index;
}
void* data = reinterpret_cast<size_t*>(dynamicUniformData[currentImage]) + normalUBOAlignment / sizeof(size_t);
memcpy(data, uboDynamic.model, ComponentManager::GetInstance()->activeComponents[TRANSFORM].size() * dynamicAlignment);
// Flush to make changes visible to the host
//VkMappedMemoryRange memoryRange = {};
//memoryRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
//memoryRange.memory = dynamicUniformBuffersMemory[currentImage];
//memoryRange.size = VK_WHOLE_SIZE;
//vkFlushMappedMemoryRanges(device, 1, &memoryRange);
}
flush不知道有啥用,据说有些显卡必须要flush才会可见,我的机器都一样,所以上面都加了VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,试了下都去掉也不影响正常的uniform...
- command buffer里面逐一画出来
for (uint32_t j = 0; j < ComponentManager::GetInstance()->activeComponents[TRANSFORM].size(); ++j)
{
uint32_t dynamicOffset = j * static_cast<uint32_t>(dynamicAlignment);
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 1, &dynamicOffset);
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), INSTANCE_COUNT, 0, 0, 0);
}
我申请buffer的时候是按照物体最大数量的大小来申请的,但是画的时候只画实际生效的物体数量,因为数量是录在command buffer里的,所以一旦增删物体需要重新录制。
void VulkanAPI::flushCommandBuffer()
{
vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
createCommandBuffers();
}
(暂时hard code了一下运动,之前还做了一下INSTANCE DRAW所以是几个方块重叠的)