Vulkan - 飞行中的多个帧

Vulkan - Multiple frames in flight

我正在做一个 Vulkan 项目,并且已经完成了绘制立方体的进度。我是 Vulkan 的新手,必须重新学习所有内容。我遇到了一些麻烦。

我想在飞行中有多个帧(1 个记录,1 个执行)。我的渲染循环是基于 https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation 的,因为我无法理解当我有第二帧时发生了什么。现在,它没有崩溃,但它似乎也不能正常工作,因为 'currentFrame' 和 'imageIndex' 是一样的。如果我将 currentFrame 或 imageIndex 更改为不同,则没有任何效果。我应该注意,如果重要的话,我现在为每个池创建 N VkCommandPools 和 1 个命令缓冲区。

初始化:

VkFenceCreateInfo fence_info;
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.pNext = NULL;
fence_info.flags = 0;

VkFence *inFlightFences = malloc(sizeof *inFlightFences * NUM_SWAPCHAIN_IMAGES);

for(i = 0; i < NUM_SWAPCHAIN_IMAGES; i++) {
    result = vkCreateFence(device.logical, &fence_info, NULL, &inFlightFences[i]);
    assert(result == VK_SUCCESS && "vkCreateFence");
}
VkFence imagesInFlight[2] = {NULL, NULL};
unsigned int imageIndex, currentFrame = 0;

主循环:

    vkAcquireNextImageKHR(device.logical, swapchain.handle, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

    printf("%u %u\n", currentFrame, imageIndex); // both equal always
    
    if(imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
        vkWaitForFences(device.logical, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
    }
    
    imagesInFlight[imageIndex] = inFlightFences[currentFrame];
    
    VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
    VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]};

    /* begin rendering */
    
    VkCommandBufferBeginInfo begin_info = {0};
    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    
    vkBeginCommandBuffer(main_cmdbuffs[imageIndex], &begin_info);
    
    VkImageMemoryBarrier acquire_barrier;
    acquire_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    acquire_barrier.pNext = NULL;
    acquire_barrier.srcAccessMask = 0;
    acquire_barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
    acquire_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    acquire_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    acquire_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    acquire_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    acquire_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    acquire_barrier.subresourceRange.baseMipLevel = 0;
    acquire_barrier.subresourceRange.levelCount = 1;
    acquire_barrier.subresourceRange.baseArrayLayer = 0;
    acquire_barrier.subresourceRange.layerCount = 1;
    acquire_barrier.image = swapchain.images[imageIndex];
    
    vkCmdPipelineBarrier(main_cmdbuffs[imageIndex],
                         VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
                         VK_PIPELINE_STAGE_TRANSFER_BIT,
                         0, 0, NULL, 0, NULL, 1, &acquire_barrier);        

    VkImageMemoryBarrier present_barrier = {};
    present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    present_barrier.pNext = NULL;
    present_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    present_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    present_barrier.subresourceRange.baseMipLevel = 0;
    present_barrier.subresourceRange.levelCount = 1;
    present_barrier.subresourceRange.baseArrayLayer = 0;
    present_barrier.subresourceRange.layerCount = 1;
    present_barrier.image = swapchain.images[imageIndex];
    
    vkCmdPipelineBarrier(main_cmdbuffs[imageIndex], VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL,
                        0, NULL, 1, &present_barrier);
    vkEndCommandBuffer(main_cmdbuffs[imageIndex]);

    VkPipelineStageFlags wait_dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    VkSubmitInfo submit_info = {0};
    
    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submit_info.pWaitDstStageMask = &wait_dst_stage_mask;
    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &main_cmdbuffs[imageIndex];
    submit_info.waitSemaphoreCount = 1;
    submit_info.signalSemaphoreCount = 1;
    submit_info.pWaitSemaphores = waitSemaphores;
    submit_info.pSignalSemaphores = signalSemaphores;
    
    vkResetFences(device.logical, 1, &inFlightFences[currentFrame]);
    
    vkQueueSubmit(device.graphics_queue, 1, &submit_info, inFlightFences[imageIndex]);
    
    VkPresentInfoKHR present_info = {0};
    present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    present_info.waitSemaphoreCount = 1;
    present_info.pWaitSemaphores = &renderFinishedSemaphores[currentFrame];
    present_info.swapchainCount = 1;
    present_info.pSwapchains = &swapchain.handle;
    present_info.pImageIndices = &currentFrame;
    
    vkQueuePresentKHR(device.graphics_queue, &present_info);

这可能会失败的原因有多种 - 所提供的信息量无法判断。最重要的事情是激活验证层,尝试理解错误消息并摆脱它(也有助于 post 此处的验证层错误!)。

您可以进一步调查的一些要点:

从给出的源代码来看,您似乎是 re-recording 命令缓冲每一帧。如果您这样做,则必须确保:

commandBuffer must have been allocated from a pool that was created with the VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT

(引用规范)。并且必须调用 vkResetCommandBuffer call somewhere. What Vulkan Tutorial 正在做的是:记录一次命令缓冲区(一个用于飞行中的每一帧)并在每一帧重复使用那些 pre-recorded 命令缓冲区。

关于 currentFrameimageIndex:在最佳情况下,它们会同步,因为

vkWaitForFences(device.logical, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);

永远不必等待 --- 也就是说,如果您之前已经等待 inFlightFences[currentFrame],这是 Vulkan 教程所做的,但如果您也这样做并不明显。

您无法真正控制得到哪个 imageIndex,因为它是从 vkAcquireNextImageKHR 返回的。但是,您必须推进 currentFrame ,如果您从 posted.

的代码中这样做的话,这又是不明显的

为您正在创建的每个 VkCommandBuffer 创建一个 VkCommandPool(或换句话说:从命令池中仅分配一个命令缓冲区)是一种次优策略。我认为,如果您只有一种类型的命令缓冲区(即设置了 VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT 位的命令缓冲区,如果您真的要重置命令缓冲区,则没有什么可以反对仅使用一个 VkCommandPool )。如果您要创建不同类型的命令缓冲区(例如可重置的和 non-resettable 的),那么不同的池可能是有意义的。