前言
上一部分,通过十个章节的内容,对视景器的初始化与准备工作进行了系统性的剖析。本章将在该基础上,探讨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 frame
if (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 frame
auto 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 indices
if (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 frame
if (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_KHR
if (_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 indices
for (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 frames
for (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实现轮询调用。
文末:本章在上一篇文章的基础上,整体介绍帧循环的内容,同时通过代码深入分析前进到下一帧的详细过程,其本质是获取交换链中可用的图像索引,为后续渲染绘制提供目标表面。下章将分析事件处理部分。