Vulkan迷惑问题-交换链中获取图片vkAcquireNextImageKHR的ImageIndex 与 currentFrame之间的关系

Posted 赵新政

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vulkan迷惑问题-交换链中获取图片vkAcquireNextImageKHR的ImageIndex 与 currentFrame之间的关系相关的知识,希望对你有一定的参考价值。

	在**vulkan**当中,我们遇到了**drawFrame**函数,但是其中有**inFlight**的概念,那么如何理解这个概念,又如何理解在这个概念之下的同步控制(**fence, semaphore**)呢?

遇到的问题

在学习VulkanTutorial的时候,同学们一定会遇到drawFrame这个函数,在这个函数里面,绘制的过程是长得这样子的:

 	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);

  	uint32_t imageIndex;
    vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);


    if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
        vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
    }
    imagesInFlight[imageIndex] = inFlightFences[currentFrame];

    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

    VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
    VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = waitSemaphores;
    submitInfo.pWaitDstStageMask = waitStages;

    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffers[imageIndex];

    VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = signalSemaphores;

    vkResetFences(device, 1, &inFlightFences[currentFrame]);

    if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
        throw std::runtime_error("failed to submit draw command buffer!");
    }

    VkPresentInfoKHR presentInfo{};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = signalSemaphores;

    VkSwapchainKHR swapChains[] = { swapChain };
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = swapChains;

    presentInfo.pImageIndices = &imageIndex;

    vkQueuePresentKHR(presentQueue, &presentInfo);

    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT

其中我们对代码进行一下简化,把其中的同步设置抽取出来看看:

  1. 进行了对于Fence的阻塞等待,用了currentFrame作为imageIndex来索引Fence
  2. 使用vkAcquireNextImageKHR来获取下一帧可以使用的图片,并且绑定一个Semaphore,用了currentFrame作为index来索引Semaphore
  3. 对提交信息等进行设置。
  4. currentFrame ++。

但是这个里面的ImageIndexcurrentFrame之间到底是什么关系呢?

同步控制

这段代码的大前提是,我们希望自己控制最多有多少张图片处于同时被渲染的状态。我们希望多少张,就做出来多少套Semaphore/Fence这种控制资源,然后用做出来的这N套资源,每一次选择一套,分配给第imageIndex张图片
我们可以先观察下所有的同步变量:

inFlightFences:这个数组的大小是MAX_FRAMES_IN_FLIGHT这么多,用来表示当前的Frame所对应的Fence
imagesInFlight:这个数组的大小是SwapChainImageCount这么多,用来表示当前某一个交换链当中图片所对应的Fence

这两个数组其实每一帧都会设置一次对应关系,imagesInFlight数组当中的每一个fence都对应了inFlightFences当中的某一个fence
接下来我们来分析下同步代码;

首先我们走到了当前第currentFrame个帧,那么,就要等待这一帧的命令队列执行完毕,为什么要等待呢?因为我们需要使用这个Fence,为接下即将获取的第imageIndex的图片进行过程控制,如下所示:
vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
只要这一句代码通过了,那么说明第currentFrame所对应的不管Fence也好、Semaphore也好,都已经使用完毕了,这一套资源对应的过程结束了。

然后我们获取到了一张当前可以用的交换链当中的图片ID

uint32_t **imageIndex**;
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

那么接下来就要使用交换链当中的第imageIndex个图片进行渲染,所以我们需要给到这张图片一组同步对象,就是当前currentFrame对应的semaphorefence, 但是还有一个问题,我们必须等待imageIndex这一帧渲染完毕才能使用,那这一张图片对应的fence就必须执行完毕,除非这一帧没有被执行过,所以就判断一下**imagesInFlight[imageIndex]**是否被赋值过。

 if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
        vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
    }
    imagesInFlight[imageIndex] = inFlightFences[currentFrame];

如上所示,在判断imageIndex对应的一帧已经执行完毕之后,我们把currentFrame对应的fence分配过去了。
接下来我们可以看到:

VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;

submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];

VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;

因为我们之前等待了inFlightFences[currentFrame]完成,所以对应的那一次提交所使用的semaphore们也一定已经使用完毕,所以可以直接给这一帧使用,即给到了imageIndex对应的交换链图片。

结论

inFlightFencesimageAvailableSemaphores以及renderFinishedSemaphores都是我们提供的资源,数量就是MAX_FRAMES_IN_FLIGHT这么多,在每一次获取imageIndex的交换链图片的时候,会看看有没有闲置资源,如果有就分配给这一帧,否则就等待currentFrame所对应的资源组空闲。

以上是关于Vulkan迷惑问题-交换链中获取图片vkAcquireNextImageKHR的ImageIndex 与 currentFrame之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

Vulkan Tutorial 08 交换链

[译]Vulkan教程(20)重建交换链

Vulkan Tutorial 18 重构交换链

这是 g++ for 循环实现还是我的代码中的错误

别被扩展系统ID迷惑了

Vulkan Tutorial 15 Framebuffers