vulkanscenegraph显示倾斜模型(6)-帧循环
前言
上一部分,通过十个章节的内容,对视景器的初始化与准备工作进行了系统性的剖析。本章将在该基础上,探讨vsg中的帧循环机制,主要包含前进到下一帧、事件处理、更新、记录与提交、呈现五个部分,同时整个过程包含了复杂的多线程处理与同步操作,本章作为帧循环部分的第一章,在整体介绍帧循环的内容的同时,将深入分析前进到下一帧的详细过程。
目录
- 1 帧循环
- 2 前进到下一帧
本章对参照用例(vulkanscenegraph显示倾斜模型-CSDN博客)中的如下代码进行深入探讨。
while (true){// render VulkanSceneGraph frame {if (!vsg_viewer->advanceToNextFrame()){break;}vsg_viewer->handleEvents();vsg_viewer->update();vsg_viewer->recordAndSubmit();vsg_viewer->present();}frameNumber++;}
1 帧循环
与OSG(OpenSceneGraph)帧循环中经典的事件遍历(Event Traversal)、更新遍历(Update Traversal)和渲染遍历(Rendering Traversal)类似,vsg帧循环中涉及同样的操作。VSG的事件处理阶段对应OSG的事件遍历(Event Traversal),负责处理用户输入和系统事件;更新阶段对应OSG的更新遍历(Update Traversal),完成场景数据的同步更新;而记录与提交阶段(包含资源编译、命令缓冲录制与提交)和最终的呈现阶段,则共同对应了OSG渲染遍历(Rendering Traversal),最终将渲染结果输出到显示窗口。这种设计即保留了OSG遍历流程的核心逻辑,同时兼顾Vulkan渲染的特点,采用更精细的阶段划分。
2 前进到下一帧
本小结对应的代码如下,接下来将通过代码来揭秘此过程的详细流程。
if (!vsg_viewer->advanceToNextFrame()){break;}
上述代码为vsg::Viewer中的advanceToNextFrame函数的调用。
bool Viewer::advanceToNextFrame(double simulationTime)
{static constexpr SourceLocation s_frame_source_location{"Viewer advanceToNextFrame", VsgFunctionName, __FILE__, __LINE__, COLOR_VIEWER, 1};// signal to instrumentation the end of the previous frameif (instrumentation && _frameStamp) instrumentation->leaveFrame(&s_frame_source_location, frameReference, *_frameStamp);if (!active()){return false;}// poll all the windows for events.pollEvents(true);if (!acquireNextFrame()) return false;// create FrameStamp for frameauto time = vsg::clock::now();if (_firstFrame){_firstFrame = false;if (simulationTime == UseTimeSinceStartPoint) simulationTime = 0.0;// first frame, initialize to frame count and indices to 0_frameStamp = FrameStamp::create(time, 0, simulationTime);}else{// after first frame so increment frame count and indicesif (simulationTime == UseTimeSinceStartPoint){simulationTime = std::chrono::duration<double, std::chrono::seconds::period>(time - _start_point).count();}_frameStamp = FrameStamp::create(time, _frameStamp->frameCount + 1, simulationTime);}// signal to instrumentation the start of frameif (instrumentation) instrumentation->enterFrame(&s_frame_source_location, frameReference, *_frameStamp);for (auto& task : recordAndSubmitTasks){task->advance();}// create an event for the new frame._events.emplace_back(new FrameEvent(_frameStamp));return true;
}
上述代码为Viewer.cpp(155-206行)对应的代码。主要把包括轮询所有窗口的事件(pollEvents)、获取下一帧(acquireNextFrame)、记录和提交任务advace三部分。其中轮询所有窗口的事件并将事件放置到_events中,供后续事件处理阶段使用。
bool Viewer::acquireNextFrame()
{CPU_INSTRUMENTATION_L1_NC(instrumentation, "Viewer acquireNextFrame", COLOR_VIEWER);if (_close) return false;VkResult result = VK_SUCCESS;for (auto& window : _windows){if (!window->visible()) continue;while ((result = window->acquireNextImage()) != VK_SUCCESS){if (result == VK_ERROR_SURFACE_LOST_KHR ||result == VK_ERROR_OUT_OF_DATE_KHR ||result == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT ||result == VK_SUBOPTIMAL_KHR){// force a rebuild of the Swapchain by calling Window::resize();window->resize();}else if (result == VK_ERROR_DEVICE_LOST){// a lost device can only be recovered by opening a new VkDevice, and success is not guaranteed.// not currently implemented, so exit main loop.warn("window->acquireNextImage() VkResult = VK_ERROR_DEVICE_LOST. Device loss can indicate invalid Vulkan API usage or driver/hardware issues.");break;}else{warn("window->acquireNextImage() VkResult = ", result);break;}}}return result == VK_SUCCESS;
}
获取下一帧(acquireNextFrame)主要调用所有窗口(vsg::Window)的acqiureNextImage方法。
VkResult Window::acquireNextImage(uint64_t timeout)
{if (!_swapchain) _initSwapchain();if (!_availableSemaphore) _availableSemaphore = vsg::Semaphore::create(_device, _traits->imageAvailableSemaphoreWaitFlag);// check the dimensions of the swapchain and window extents are consistent, if not return a VK_ERROR_OUT_OF_DATE_KHRif (_swapchain->getExtent() != _extent2D) return VK_ERROR_OUT_OF_DATE_KHR;uint32_t nextImageIndex;VkResult result = _swapchain->acquireNextImage(timeout, _availableSemaphore, {}, nextImageIndex);if (result == VK_SUCCESS){// the acquired image's semaphore must be available now so make it the new _availableSemaphore and set its entry to the one to use for the next frame by swapping ref_ptr<>'s_availableSemaphore.swap(_frames[nextImageIndex].imageAvailableSemaphore);// shift up previous frame indicesfor (size_t i = _indices.size() - 1; i > 0; --i){_indices[i] = _indices[i - 1];}// update head of _indices to the new frames nextImageIndex_indices[0] = nextImageIndex;}else{vsg::debug("Window::acquireNextImage(uint64_t timeout) _swapchain->acquireNextImage(...) failed with result = ", result);}return result;
}
Window::acquireNextImage函数通过调用vsg::SwapChain的acquireNextImage方法,获取图像索引,当调用返回为VK_SUCCESS时,将信号量交换到当前帧,同时采用轮询的方式将当前图像索引置于索引数组_indices的头部。
struct Frame{ref_ptr<ImageView> imageView;ref_ptr<Framebuffer> framebuffer;ref_ptr<Semaphore> imageAvailableSemaphore;};
当前帧的数据结构如上代码所示,包含图像视图、帧缓存、图像可提供信号量。
for (auto& window : windows){auto imageIndex = window->imageIndex();if (imageIndex >= window->numFrames()) continue;auto& semaphore = window->frame(imageIndex).imageAvailableSemaphore;vk_waitSemaphores.emplace_back(*semaphore);vk_waitStages.emplace_back(semaphore->pipelineStageFlags());}for (auto& semaphore : waitSemaphores){vk_waitSemaphores.emplace_back(*(semaphore));vk_waitStages.emplace_back(semaphore->pipelineStageFlags());}current_fence->dependentSemaphores() = signalSemaphores;for (auto& semaphore : signalSemaphores){vk_signalSemaphores.emplace_back(*(semaphore));}if (earlyDataTransferredSemaphore){vk_signalSemaphores.emplace_back(earlyTransferConsumerCompletedSemaphore->vk());current_fence->dependentSemaphores().push_back(earlyTransferConsumerCompletedSemaphore);}if (lateDataTransferredSemaphore){vk_signalSemaphores.emplace_back(lateTransferConsumerCompletedSemaphore->vk());current_fence->dependentSemaphores().push_back(lateTransferConsumerCompletedSemaphore);}VkSubmitInfo submitInfo = {};submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;submitInfo.waitSemaphoreCount = static_cast<uint32_t>(vk_waitSemaphores.size());submitInfo.pWaitSemaphores = vk_waitSemaphores.data();submitInfo.pWaitDstStageMask = vk_waitStages.data();submitInfo.commandBufferCount = static_cast<uint32_t>(vk_commandBuffers.size());submitInfo.pCommandBuffers = vk_commandBuffers.data();submitInfo.signalSemaphoreCount = static_cast<uint32_t>(vk_signalSemaphores.size());submitInfo.pSignalSemaphores = vk_signalSemaphores.data();return queue->submit(submitInfo, current_fence);
如上代码为RecordAndSubmitTask.cpp中218-265行代码,从上述代码可知当前帧中的图像可提供信号量,用于确保队列提交顺序的正确性,即先获取交换链图像索引,然后提交命令。
回到vsg::Viewer中的advanceToNextFrame函数,函数执行的第三部分为记录和提交任务advace,具体代码如下:
void RecordAndSubmitTask::advance()
{CPU_INSTRUMENTATION_L1_NC(instrumentation, "RecordAndSubmitTask advance", COLOR_VIEWER);// info("\nRecordAndSubmitTask::advance()");if (_currentFrameIndex >= _indices.size()){// first frame so set to 0_currentFrameIndex = 0;}else{++_currentFrameIndex;if (_currentFrameIndex > _indices.size() - 1) _currentFrameIndex = 0;// shift the index for previous framesfor (size_t i = _indices.size() - 1; i >= 1; --i){_indices[i] = _indices[i - 1];}}// pass the index for the current frame_indices[0] = _currentFrameIndex;
}
RecordAndSubmitTask::advance函数同样采用轮询的方式将当前帧的索引放置在首位,_indices索引数组与栅栏数组_fences配合使用,从而使_fences实现轮询调用。
文末:本章在上一篇文章的基础上,整体介绍帧循环的内容,同时通过代码深入分析前进到下一帧的详细过程,其本质是获取交换链中可用的图像索引,为后续渲染绘制提供目标表面。下章将分析事件处理部分。