Vulkan:渲染时内存泄漏
Vulkan: Memory leak when rendering
我正在用 vulkan 做一个简单程序,只是为了开始。我正在清除背景颜色,就是这样。问题是程序每一帧都会分配越来越多的内存,我不知道从哪里来。
bool VulkanRenderer::Update()
{
PrepareFrame(); ///--- < Commenting this
SubmitFrame(); ///--- < and this avoids memory leak
}//Update
这是另外两个函数,当它们不被调用时,程序的内存保持不变。
void VulkanRenderer::PrepareFrame()
{
///--- Reset command buffers
vkResetCommandPool(m_pDevice, m_pCoreCommandPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT);
VkResult iRes;
// Get the index of the next available swapchain image:
iRes=m_oSwapChain.AcquireNextImage(m_oSemaphorePresentReady, &m_uSwapChainImage);
if(iRes!=VK_SUCCESS){
CheckVulkanError(iRes);
}
///---------------------------------
/// Convert image to drawable
///---------------------------------
VkCommandBufferBeginInfo oCmdBegin={};
oCmdBegin.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
///---------------------------------
/// Prepare primary command buffer
///---------------------------------
//vkFreeCommandBuffers(m_pDevice, m_pCoreCommandPool, 1, &m_oPrimaryCmd);
//m_oPrimaryCmd=CreateCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
vkBeginCommandBuffer(m_oPrimaryCmd, &oCmdBegin);
{///--- Convert image to drawable
VkImageMemoryBarrier postPresentBarrier={};
postPresentBarrier.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
postPresentBarrier.srcAccessMask=VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
postPresentBarrier.dstAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
postPresentBarrier.oldLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
postPresentBarrier.newLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
postPresentBarrier.srcQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
postPresentBarrier.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
postPresentBarrier.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
postPresentBarrier.image=m_oSwapChain.images()[m_uSwapChainImage];
vkCmdPipelineBarrier(m_oPrimaryCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &postPresentBarrier);
}
{///--- Render pass
VkClearValue clearValues[2];
clearValues[0].color={{1.0f, 0.0f, 0.2f, 0.0f}};
clearValues[1].depthStencil={1.0f, 0};
VkRenderPassBeginInfo renderPassBeginInfo={};
renderPassBeginInfo.sType=VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.renderPass=m_pRenderPass;
renderPassBeginInfo.renderArea.offset.x=0;
renderPassBeginInfo.renderArea.offset.y=0;
renderPassBeginInfo.renderArea.extent.width=m_uSwapchainWidth;
renderPassBeginInfo.renderArea.extent.height=m_uSwapchainHeight;
renderPassBeginInfo.clearValueCount=2;
renderPassBeginInfo.pClearValues=clearValues;
renderPassBeginInfo.framebuffer=m_pFrameBuffers[m_uSwapChainImage];
vkCmdBeginRenderPass(m_oPrimaryCmd, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
vkCmdEndRenderPass(m_oPrimaryCmd);
}
}//PrepareFrame
和
void VulkanRenderer::SubmitFrame()
{
///---------------------------------
/// Executed submited secondary commands
///---------------------------------
vkCmdExecuteCommands(m_oPrimaryCmd, 0, nullptr);
///---------------------------------
/// Convert image to presentable
///---------------------------------
{
VkImageMemoryBarrier prePresentBarrier={};
prePresentBarrier.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
prePresentBarrier.srcAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
prePresentBarrier.dstAccessMask=VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
prePresentBarrier.oldLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
prePresentBarrier.newLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
prePresentBarrier.srcQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
prePresentBarrier.image=m_oSwapChain.images()[m_uSwapChainImage];
vkCmdPipelineBarrier(m_oPrimaryCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &prePresentBarrier);
}
vkEndCommandBuffer(m_oPrimaryCmd);
///--- Submit
VkPipelineStageFlags wait_dst_stage_mask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
VkSubmitInfo submit_info={
VK_STRUCTURE_TYPE_SUBMIT_INFO, // VkStructureType sType
nullptr, // const void *pNext
1, // uint32_t waitSemaphoreCount
&m_oSemaphorePresentReady, // const VkSemaphore *pWaitSemaphores
&wait_dst_stage_mask, // const VkPipelineStageFlags *pWaitDstStageMask;
1, // uint32_t commandBufferCount
&m_oPrimaryCmd, // const VkCommandBuffer *pCommandBuffers
1, // uint32_t signalSemaphoreCount
&m_oSemaphoreRenderComplete // const VkSemaphore *pSignalSemaphores
};
vkQueueSubmit(m_pDeviceQueue, 1, &submit_info, VK_NULL_HANDLE);
///--- Present queue
VkResult iRes;
iRes=m_oSwapChain.QueuePresent(m_pDeviceQueue, m_uSwapChainImage, m_oSemaphoreRenderComplete);
CheckVulkanError(iRes);
///--- Flush device
vkQueueWaitIdle(m_pDeviceQueue);
}//SubmitFrame
其他:
VkResult VulkanSwapchain::AcquireNextImage(VkSemaphore oPresentCompleteSemaphore, uint32_t* pCurrentBuffer)
{
///---------------------------------
/// Acquires next image in the swap chain
///---------------------------------
if(!m_fpAcquireNextImageKHR){
XLOG("%s:%d: m_fpAcquireNextImageKHR", __FUNCTION__, __LINE__);
return VkResult::VK_INCOMPLETE;
}
VkResult iRes=VkResult::VK_SUCCESS;
iRes=m_fpAcquireNextImageKHR(m_pDevice, m_pSwapChain, UINT64_MAX, oPresentCompleteSemaphore, (VkFence)nullptr, pCurrentBuffer);
return iRes;
}//AcquireNextImage
一开始我以为这是我正在分配而不是释放的一些 Vulkan 资源,但这是 Vulkan 调试层提供的:
INFORMATION: [MEM] Code 0 : Details of Memory Object list (of size 1 elements)
INFORMATION: [MEM] Code 0 : =============================
INFORMATION: [MEM] Code 0 : ===MemObjInfo at 0000000003BD6E58===
INFORMATION: [MEM] Code 0 : Mem object: 0x3bd6ac0
INFORMATION: [MEM] Code 0 : Ref Count: 1
INFORMATION: [MEM] Code 0 : Mem Alloc info:
MEM(INFO): sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
MEM(INFO): pNext = 0000000000000000
MEM(INFO): allocationSize = 2621440
MEM(INFO): memoryTypeIndex = 1
INFORMATION: [MEM] Code 0 : VK OBJECT Binding list of size 1 elements:
INFORMATION: [MEM] Code 0 : VK OBJECT 62742624
INFORMATION: [MEM] Code 0 : VK Command Buffer (CB) binding list of size 0 elements
INFORMATION: [MEM] Code 0 : Details of CB list (of size 1 elements)
INFORMATION: [MEM] Code 0 : ==================
INFORMATION: [MEM] Code 0 : CB Info (0000000003BB4228) has CB 0000000003BB2AD0, fenceId a3, and fence 0
那里的每一帧似乎只存在2个内部资源。
我还重载了 new/new[]/delete/delete[] 运算符,并且在循环期间不会调用它们。
我一直在寻找的示例几乎相同,我尝试在每一帧删除和创建主命令缓冲区,仍然得到相同的结果。
不调用 PrepareFrame() 和 SubmitFrame() 解决了这个问题。为什么?
这个分配是从哪里来的?我怎样才能找到这种分配?
正如 krOoze 所提到的,尝试更新到最新的 SDK,或者(更好地)从源代码构建图层以始终获得最新的图层。 Afaik 关于正确内存检查的层仍然存在未解决的问题,因此它们实际上可能导致内存泄漏。从您上传的源代码中的验证层名称来看,您没有使用最新的名称,例如VK_LAYER_LUNARG_threading 最近重命名为 VK_LAYER_GOOGLE_threading。
在我的示例中使用当前图层我无法重现任何内存泄漏。
但是我在你的消息来源中注意到一件事:
int iUsedLayers=0;
const char* ppLayers[64]={};
if(bExtraLayers){
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_standard_validation";
#if VKMEMDBG
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_mem_tracker";
#endif
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_threading";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_object_tracker";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_draw_state";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_param_checker";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_swapchain";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_device_limits";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_image";
ppLayers[iUsedLayers++]="VK_LAYER_GOOGLE_unique_objects";
}
虽然应该没有什么坏处,但您实际上将所有图层(mem_tracker 图层除外,具体取决于定义)添加了两次。 VK_LAYER_LUNARG_standard_validation 元层已经以正确的顺序启用了所有可用的(基本)验证层(您的订单可能不是这种情况),因此之后没有必要一一添加它们。
导致类似错误的另一个可能原因是未调用:
vkDeviceWaitIdle ( Device );
在 renders/drawframe 次通话之间。所有提交的命令缓冲区都添加到一个列表中,该列表仅从以下位置清除:
vkQueueWaitIdle, vkDeviceWaitIdle and vkWaitForFences
来电。如果您在程序中调用其中一个,统一缓冲区代码将通过 copyBuffer 调用在每一帧调用 vkDeviceWaitIdle,并且不会再从 vkQueueSubmit() 泄漏。
我正在用 vulkan 做一个简单程序,只是为了开始。我正在清除背景颜色,就是这样。问题是程序每一帧都会分配越来越多的内存,我不知道从哪里来。
bool VulkanRenderer::Update()
{
PrepareFrame(); ///--- < Commenting this
SubmitFrame(); ///--- < and this avoids memory leak
}//Update
这是另外两个函数,当它们不被调用时,程序的内存保持不变。
void VulkanRenderer::PrepareFrame()
{
///--- Reset command buffers
vkResetCommandPool(m_pDevice, m_pCoreCommandPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT);
VkResult iRes;
// Get the index of the next available swapchain image:
iRes=m_oSwapChain.AcquireNextImage(m_oSemaphorePresentReady, &m_uSwapChainImage);
if(iRes!=VK_SUCCESS){
CheckVulkanError(iRes);
}
///---------------------------------
/// Convert image to drawable
///---------------------------------
VkCommandBufferBeginInfo oCmdBegin={};
oCmdBegin.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
///---------------------------------
/// Prepare primary command buffer
///---------------------------------
//vkFreeCommandBuffers(m_pDevice, m_pCoreCommandPool, 1, &m_oPrimaryCmd);
//m_oPrimaryCmd=CreateCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
vkBeginCommandBuffer(m_oPrimaryCmd, &oCmdBegin);
{///--- Convert image to drawable
VkImageMemoryBarrier postPresentBarrier={};
postPresentBarrier.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
postPresentBarrier.srcAccessMask=VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
postPresentBarrier.dstAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
postPresentBarrier.oldLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
postPresentBarrier.newLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
postPresentBarrier.srcQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
postPresentBarrier.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
postPresentBarrier.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
postPresentBarrier.image=m_oSwapChain.images()[m_uSwapChainImage];
vkCmdPipelineBarrier(m_oPrimaryCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &postPresentBarrier);
}
{///--- Render pass
VkClearValue clearValues[2];
clearValues[0].color={{1.0f, 0.0f, 0.2f, 0.0f}};
clearValues[1].depthStencil={1.0f, 0};
VkRenderPassBeginInfo renderPassBeginInfo={};
renderPassBeginInfo.sType=VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.renderPass=m_pRenderPass;
renderPassBeginInfo.renderArea.offset.x=0;
renderPassBeginInfo.renderArea.offset.y=0;
renderPassBeginInfo.renderArea.extent.width=m_uSwapchainWidth;
renderPassBeginInfo.renderArea.extent.height=m_uSwapchainHeight;
renderPassBeginInfo.clearValueCount=2;
renderPassBeginInfo.pClearValues=clearValues;
renderPassBeginInfo.framebuffer=m_pFrameBuffers[m_uSwapChainImage];
vkCmdBeginRenderPass(m_oPrimaryCmd, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
vkCmdEndRenderPass(m_oPrimaryCmd);
}
}//PrepareFrame
和
void VulkanRenderer::SubmitFrame()
{
///---------------------------------
/// Executed submited secondary commands
///---------------------------------
vkCmdExecuteCommands(m_oPrimaryCmd, 0, nullptr);
///---------------------------------
/// Convert image to presentable
///---------------------------------
{
VkImageMemoryBarrier prePresentBarrier={};
prePresentBarrier.sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
prePresentBarrier.srcAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
prePresentBarrier.dstAccessMask=VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
prePresentBarrier.oldLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
prePresentBarrier.newLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
prePresentBarrier.srcQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
prePresentBarrier.subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
prePresentBarrier.image=m_oSwapChain.images()[m_uSwapChainImage];
vkCmdPipelineBarrier(m_oPrimaryCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &prePresentBarrier);
}
vkEndCommandBuffer(m_oPrimaryCmd);
///--- Submit
VkPipelineStageFlags wait_dst_stage_mask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
VkSubmitInfo submit_info={
VK_STRUCTURE_TYPE_SUBMIT_INFO, // VkStructureType sType
nullptr, // const void *pNext
1, // uint32_t waitSemaphoreCount
&m_oSemaphorePresentReady, // const VkSemaphore *pWaitSemaphores
&wait_dst_stage_mask, // const VkPipelineStageFlags *pWaitDstStageMask;
1, // uint32_t commandBufferCount
&m_oPrimaryCmd, // const VkCommandBuffer *pCommandBuffers
1, // uint32_t signalSemaphoreCount
&m_oSemaphoreRenderComplete // const VkSemaphore *pSignalSemaphores
};
vkQueueSubmit(m_pDeviceQueue, 1, &submit_info, VK_NULL_HANDLE);
///--- Present queue
VkResult iRes;
iRes=m_oSwapChain.QueuePresent(m_pDeviceQueue, m_uSwapChainImage, m_oSemaphoreRenderComplete);
CheckVulkanError(iRes);
///--- Flush device
vkQueueWaitIdle(m_pDeviceQueue);
}//SubmitFrame
其他:
VkResult VulkanSwapchain::AcquireNextImage(VkSemaphore oPresentCompleteSemaphore, uint32_t* pCurrentBuffer)
{
///---------------------------------
/// Acquires next image in the swap chain
///---------------------------------
if(!m_fpAcquireNextImageKHR){
XLOG("%s:%d: m_fpAcquireNextImageKHR", __FUNCTION__, __LINE__);
return VkResult::VK_INCOMPLETE;
}
VkResult iRes=VkResult::VK_SUCCESS;
iRes=m_fpAcquireNextImageKHR(m_pDevice, m_pSwapChain, UINT64_MAX, oPresentCompleteSemaphore, (VkFence)nullptr, pCurrentBuffer);
return iRes;
}//AcquireNextImage
一开始我以为这是我正在分配而不是释放的一些 Vulkan 资源,但这是 Vulkan 调试层提供的:
INFORMATION: [MEM] Code 0 : Details of Memory Object list (of size 1 elements)
INFORMATION: [MEM] Code 0 : =============================
INFORMATION: [MEM] Code 0 : ===MemObjInfo at 0000000003BD6E58===
INFORMATION: [MEM] Code 0 : Mem object: 0x3bd6ac0
INFORMATION: [MEM] Code 0 : Ref Count: 1
INFORMATION: [MEM] Code 0 : Mem Alloc info:
MEM(INFO): sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
MEM(INFO): pNext = 0000000000000000
MEM(INFO): allocationSize = 2621440
MEM(INFO): memoryTypeIndex = 1
INFORMATION: [MEM] Code 0 : VK OBJECT Binding list of size 1 elements:
INFORMATION: [MEM] Code 0 : VK OBJECT 62742624
INFORMATION: [MEM] Code 0 : VK Command Buffer (CB) binding list of size 0 elements
INFORMATION: [MEM] Code 0 : Details of CB list (of size 1 elements)
INFORMATION: [MEM] Code 0 : ==================
INFORMATION: [MEM] Code 0 : CB Info (0000000003BB4228) has CB 0000000003BB2AD0, fenceId a3, and fence 0
那里的每一帧似乎只存在2个内部资源。 我还重载了 new/new[]/delete/delete[] 运算符,并且在循环期间不会调用它们。
我一直在寻找的示例几乎相同,我尝试在每一帧删除和创建主命令缓冲区,仍然得到相同的结果。
不调用 PrepareFrame() 和 SubmitFrame() 解决了这个问题。为什么?
这个分配是从哪里来的?我怎样才能找到这种分配?
正如 krOoze 所提到的,尝试更新到最新的 SDK,或者(更好地)从源代码构建图层以始终获得最新的图层。 Afaik 关于正确内存检查的层仍然存在未解决的问题,因此它们实际上可能导致内存泄漏。从您上传的源代码中的验证层名称来看,您没有使用最新的名称,例如VK_LAYER_LUNARG_threading 最近重命名为 VK_LAYER_GOOGLE_threading。
在我的示例中使用当前图层我无法重现任何内存泄漏。
但是我在你的消息来源中注意到一件事:
int iUsedLayers=0;
const char* ppLayers[64]={};
if(bExtraLayers){
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_standard_validation";
#if VKMEMDBG
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_mem_tracker";
#endif
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_threading";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_object_tracker";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_draw_state";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_param_checker";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_swapchain";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_device_limits";
ppLayers[iUsedLayers++]="VK_LAYER_LUNARG_image";
ppLayers[iUsedLayers++]="VK_LAYER_GOOGLE_unique_objects";
}
虽然应该没有什么坏处,但您实际上将所有图层(mem_tracker 图层除外,具体取决于定义)添加了两次。 VK_LAYER_LUNARG_standard_validation 元层已经以正确的顺序启用了所有可用的(基本)验证层(您的订单可能不是这种情况),因此之后没有必要一一添加它们。
导致类似错误的另一个可能原因是未调用:
vkDeviceWaitIdle ( Device );
在 renders/drawframe 次通话之间。所有提交的命令缓冲区都添加到一个列表中,该列表仅从以下位置清除:
vkQueueWaitIdle, vkDeviceWaitIdle and vkWaitForFences
来电。如果您在程序中调用其中一个,统一缓冲区代码将通过 copyBuffer 调用在每一帧调用 vkDeviceWaitIdle,并且不会再从 vkQueueSubmit() 泄漏。