Vulkan动态Uniform Buffer渲染崩溃:循环超21次即冻结异常
Vulkan动态UBO渲染超过21次崩溃问题排查
问题现象
实现Vulkan动态Uniform Buffer(Dynamic UBO)时,循环绘制21次程序运行正常,但循环次数设为22次及以上时,程序直接冻结崩溃。设备为GTX 3060,尝试渲染数百个球体时,访问uniformBufferObject[4032](对应偏移192*21字节)时触发崩溃。bufferSize为UniformBufferObject结构体大小,该结构体包含3个glm::mat4,计算得192字节。
相关代码片段
绘制循环代码
uint32_t dynamicOffset = 0; glm::mat4 proj = glm::perspective(fov, (float)screenWidth / (float)screenHeight, zNear, zFar); proj[1][1] *= -1; glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); for (int i = 0; i < 21; i++) { dynamicOffset = bufferSize * i; // Update and draw the cube uniformBufferObject[i].proj = proj; uniformBufferObject[i].view = view; uniformBufferObject[i].model = glm::mat4(1.0f); uniformBufferObject[i].model = glm::translate(uniformBufferObject[i].model, glm::vec3(0.0f, 0.0f, -3.0f)); uniformBufferObject[i].model = glm::rotate(uniformBufferObject[i].model, 0.0f, glm::vec3(1.0f, 1.0f, 1.0f)); uniformBufferObject[i].model = glm::scale(uniformBufferObject[i].model, glm::vec3(0.45f)); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 0, 1, &descriptorSet, 1, &dynamicOffset); vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertices.size()), 1, 0, 0); }
Uniform Buffer创建代码
void createUniformBuffer() { createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffer, uniformBufferMemory); vkMapMemory(device, uniformBufferMemory, 0, bufferSize, 0, &uniformBufferMapped); uniformBufferObject = static_cast<UniformBufferObject*>(uniformBufferMapped); }
Create Buffer函数
void createBuffer(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(memRequirements.memoryTypeBits, properties); vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory); vkBindBufferMemory(device, buffer, bufferMemory, 0); }
findMemoryType函数
uint32_t 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; } } }
UBO结构体与描述符相关代码
struct UniformBufferObject { glm::mat4 model; glm::mat4 view; glm::mat4 proj; }; VkDescriptorSetLayoutBinding uboLayoutBinding{}; uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorCount = 1; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; std::array<VkDescriptorPoolSize, 2> poolSizes{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; poolSizes[0].descriptorCount = static_cast<uint32_t>(1); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = static_cast<uint32_t>(1); descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[0].dstSet = descriptorSet; descriptorWrites[0].dstBinding = 0; descriptorWrites[0].dstArrayElement = 0; descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; descriptorWrites[0].descriptorCount = 1; descriptorWrites[0].pBufferInfo = &bufferInfo;
问题原因与修复方案
核心问题
- Uniform Buffer内存容量不足:创建Buffer时仅分配了单个UBO的内存(192字节),但循环中尝试访问第22个实例时,偏移量达到192*21=4032字节,远超实际分配的内存,导致内存越界访问触发崩溃。
- 动态偏移未满足对齐要求:Vulkan要求动态UBO的偏移量必须是设备
minUniformBufferOffsetAlignment的整数倍,GTX3060的该值为256字节,当前192字节的UBO大小作为偏移步长违反规范,可能引发设备异常。
修复步骤
1. 扩容Uniform Buffer内存
根据需要支持的最大实例数分配足够内存,同时对齐到设备要求的最小偏移量:
// 假设需要支持1000个实例 const uint32_t maxInstanceCount = 1000; VkPhysicalDeviceProperties deviceProps; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProps); uint32_t minUboAlign = deviceProps.limits.minUniformBufferOffsetAlignment; // 计算单个UBO的对齐后大小 VkDeviceSize alignedUboSize = (sizeof(UniformBufferObject) + minUboAlign - 1) & ~(minUboAlign - 1); // 总缓冲区大小 VkDeviceSize totalBufferSize = alignedUboSize * maxInstanceCount; // 创建扩容后的Buffer createBuffer(totalBufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffer, uniformBufferMemory); // 映射整个缓冲区内存 vkMapMemory(device, uniformBufferMemory, 0, VK_WHOLE_SIZE, 0, &uniformBufferMapped); uniformBufferObject = static_cast<UniformBufferObject*>(uniformBufferMapped);
2. 使用对齐后的偏移量
循环中计算动态偏移时,改用对齐后的UBO大小作为步长:
// 替换原bufferSize为alignedUboSize dynamicOffset = alignedUboSize * i;
3. 更新描述符缓冲区信息
确保描述符写入时指向整个大缓冲区:
VkDescriptorBufferInfo bufferInfo{}; bufferInfo.buffer = uniformBuffer; bufferInfo.offset = 0; bufferInfo.range = totalBufferSize; // 或使用VK_WHOLE_SIZE
内容的提问来源于stack exchange,提问作者Metal Jesus
相关产品推荐
相关产品推荐

