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 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实现轮询调用。

文末:本章在上一篇文章的基础上,整体介绍帧循环的内容,同时通过代码深入分析前进到下一帧的详细过程,其本质是获取交换链中可用的图像索引,为后续渲染绘制提供目标表面。下章将分析事件处理部分。

资源下载链接为: https://pan.quark.cn/s/67c535f75d4c Linux 中的 top 命令是一个功能强大的实时监控工具,能够详细展示系统资源的使用情况,涵盖 CPU、内存和进程等方面。本文将深入剖析 top 命令的输出内容及其含义,帮助大家更好地掌握这一工具的使用。 top 命令的输出大致可以分为以下几部分:系统状态、CPU 使用情况、内存使用情况、进程列表以及其他信息。 系统状态部分包括以下内容: 当前时间:例如“11:00:54”,表示系统当前的时间。 系统运行时间:如“up 54 days, 23:35”,表示系统已经连续运行了多长时间。 登录用户:例如“6 users”,显示当前登录到系统的用户数量。 负载平均值:例如“load average: 16.32, 18.75, 21.04”,分别表示过去 1 分钟、5 分钟和 15 分钟的平均负载。这个数值反映了系统处理任务的压力。如果负载平均值持续高于 CPU 核心数的 70%,可能意味着系统处于过载状态。 CPU 使用情况部分显示各 CPU 核心的使用情况,例如“29.7 us, 18.9 sy, 0.0 ni, 49.3 id, 1.7 wa, 0.0 hi, 0.4 si, 0.0 st”,其中: “us”表示用户空间的 CPU 使用率; “sy”表示内核空间的 CPU 使用率; “ni”表示优先级调整的 CPU 使用率; “id”表示空闲的 CPU 使用率; “wa”表示等待 I/O 完成的 CPU 使用率; “hi”表示硬件中断的 CPU 使用率; “si”表示软件中断的 CPU 使用率; “st”表示被停止的进程的 CPU 使用率。 内存使用情况部分包括: KiB Mem:显示内存的总量、空闲量、已使用量以及缓存/缓冲区量,例如“32781216 total, 1506220
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CHPCWWHSU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值