08/09/2020
由于Vulkan大多数操作都是多线程,如果某些步骤需要同步,即按照顺序执行,我们需要启动同步机制。
信号量用于在命令队列内或者跨命令队列同步操作,我们需要同步绘制与呈现的队列操作。
两个信号量,一个表示准备进行渲染,另一个表示渲染结束,准备呈现
VkSemaphore imageAvailableSemaphore;
VkSemaphore renderFinishedSemaphore;
void createSemaphores()
{
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS)
{
throw std::runtime_error("failed to create semaphores!");
}
}
//acquire an image from the swap chain
uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore,
VK_NULL_HANDLE, &imageIndex);
首先因为交换链是一个扩展,我们需要使用vkXXXKHR来使用扩展函数。
基本逻辑:
//submit the command buffer
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
//参数指定在执行开始之前要等待的哪个信号量及要等待的通道的哪个阶段
VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; //等待图像状态变为available
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
//应该提交命令缓冲区,它将我们刚获取的交换链图像做为颜色附件进行绑定。
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; //提交命令缓冲区
//当命令缓冲区执行结束向哪些信号量发出信号
VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
//向图像队列提交命令缓冲区
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
{
throw std::runtime_error("failed to submit draw command buffer!");
}
填写VkSubmitInfo信息:
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
//等待的信号量
VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
前三个参数指定执行前要等待的信号量以及要在管道的哪个阶段中等待。我们要等到将颜色写入图像后再使用,因此我们要指定写入颜色附件的图形管道的阶段。这意味着从理论上讲,该实现已经可以开始执行我们的顶点着色器在图像还不能使用时候
//指定了当命令缓冲区执行结束向哪些信号量发出信号
VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
signalSemaphoreCount和pSignalSemaphores参数指定在命令缓冲区完成执行后要发信号的信号量。在本例中,我们为此使用renderFinishedSemaphore。
vkQueueSubmit:当工作量大得多时,该函数将VkSubmitInfo结构的数组作为效率的参数。最后一个参数引用一个可选的fences,该fences将在命令缓冲区完成执行时发出信号。我们正在使用信号量进行同步,因此我们不需要fences
//Presentation
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
//需要等待的信号量renderFinished
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
presentInfo.pResults = nullptr;
vkQueuePresentKHR(presentQueue,&presentInfo);
窗口关闭时候,有可能绘图和呈现指令还在运转,导致程序问题
void mainLoop()
{
while(!glfwWindowShouldClose(window))
{
glfwPollEvents();
drawFram();
}
vkDeviceWaitIdle(device); //等待设备空闲
}
vkQueuePresentKHR(presentQueue,&presentInfo);
vkQueueWaitIdle(presentQueue);
但意味着图像管道每次都只绘制一帧图像,其实某些管道是处于空闲状态的,完全可以加以利用于下一帧。
可以每次绘制两帧的内容,但依然没有解决CPU-GPU问题
const int MAX_FRAMES_IN_FLIGHT = 2;
size_t currentFrame = 0;
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
部分修改
void drawFrame()
{
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX,
imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE,
&imageIndex);
VkSemaphore waitSemaphores[] ={imageAvailableSemaphores[currentFrame]};
VkSemaphore signalSemaphores[] ={renderFinishedSemaphores[currentFrame]};
currentFrame = (currentFrame+1)%MAX_FRAMES_IN_FLIGHT;
}
std::vector<VkFence> inFlightFences;
std::vector<VkFence> imagesInFlight;
void createSyncObjects() {
imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); //大于交换链数量怎么办
renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
//用于限制索引混乱
imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE); //跟踪每个正在使用的图像
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; //Fence 一开始处于有信号状态
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create synchronization objects for a frame!");
}
}
}
void drawFrame() {
//等待fences这一帧呈现完成,才可以执行下一步
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]);
//命令缓冲区完成这一帧的绘制,标志fences在这一帧正在前往呈现的路上,不允许再被使用
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;
}