c++ 忘记调用ImGui::NewFrame()?

c7rzv4ha  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(131)

我正在尝试使用SFML和imgui-SFML做一个项目。我是imgui的新手,不确定问题到底出在哪里。从我收集的信息来看,imgui::render必须与imgui::update发生在同一时间步,这在SFML和正确处理Delta时间的上下文中是有限的和次优的。
下面是我的问题的一些最小的例子:

#include <SFML/Graphics.hpp>
#include "ImGui.h"
#include "ImGui-SFML.h"

void update(sf::Time dt, sf::RenderWindow& window);
void processEvents(sf::RenderWindow& window);
void render(sf::RenderWindow& window);

int main()
{
    sf::RenderWindow window;
    window.create(sf::VideoMode(800, 600), "window");
    ImGui::SFML::Init(window);

    sf::Clock clock;
    sf::Time timeSinceLastUpdate = sf::Time::Zero;
    sf::Time mTimePerFrame = sf::seconds(1.f / 60.f);

    while (window.isOpen())
    {
        sf::Time elapsedTime = clock.restart();
        timeSinceLastUpdate += elapsedTime;
        while (timeSinceLastUpdate > mTimePerFrame)
        {
            timeSinceLastUpdate -= mTimePerFrame;
            processEvents(window);
            update(mTimePerFrame, window);
        }
        render(window);
    }
    return 0;
}

void update(sf::Time dt, sf::RenderWindow& window)
{
    ImGui::SFML::Update(window, dt);
    ImGui::Begin("Hello!", NULL);
    ImGui::Button("click me", ImVec2(32, 32));
    ImGui::End();
}

void processEvents(sf::RenderWindow& window)
{
    sf::Event event;
    while (window.pollEvent(event))
    {
        ImGui::SFML::ProcessEvent(event);
    }
}

void render(sf::RenderWindow& window)
{
    window.clear();
    ImGui::SFML::Render();
    window.display();
}

字符串
如果我删除第二个while循环(while(timeSinceLastUpdate > mTimePerFrame)),一切都正常工作,但这会使我的游戏依赖于帧率,这显然是我想避免的。

3zwtqj6y

3zwtqj6y1#

这个Assert触发了关于新帧的错误,因为updateImGui::SFML::Update(调用ImGui::NewFrame)在第一次调用render之前不会被调用(它最终调用上面链接的ImGui::EndFrame)。尽管有一种机制可以用固定的时间步长调用update,但外部循环实际上没有任何帧限制机制,因此,发生的情况(或者说,如果没有Assert失败,将会发生的情况)是,在外部循环的大多数迭代中,内部循环没有进入(timeSinceLastUpdate还没有累积),但render被调用。
解决这个问题的一个简单方法是确保render只在游戏状态更新时被调用(即内部循环至少有一次迭代)我们可以通过将内部循环从while转换为if-do-while并将render(window);移动到if内部来实现这一点。这不仅解决了ImGui::NewFrame问题(因为现在render执行的时候它已经被调用了),但是节省了不必要的渲染/绘制未更新的游戏状态。
但是,现在game(outer)循环只是不断迭代并不必要地保持线程忙碌繁忙,同时在timeSinceLastUpdate最终超过固定时间阈值之前每隔几微秒检查一次时间(如果不是更频繁的话)。相反,我们可以在进入if之前将线程休眠大约所需的时间,这可以在例如else分支中完成:

while (window.isOpen())
{
    sf::Time elapsedTime = clock.restart();
    timeSinceLastUpdate += elapsedTime;
    if (timeSinceLastUpdate > mTimePerFrame)
    {
        do
        {
            timeSinceLastUpdate -= mTimePerFrame;
            processEvents(window);
            update(mTimePerFrame, window);
        } while (timeSinceLastUpdate > mTimePerFrame);
        render(window);
    }
    else
    {
        sf::sleep(mTimePerFrame - timeSinceLastUpdate /* - clock.getElapsedTime() which is negligible here */);
    }
}

字符串
请注意,在SFML中,帧限制通常不是由sf::sleep完成,而是由sf::Window::setVerticalSyncEnabled完成(在GPU驱动程序级别实现VSync)或sf::Window::setFramerateLimit(它使用sf::sleepsf::Window自己的内部时钟),其中后者与我们所做的特别相似。但是这两个函数都在sf::Window::display内部阻塞。(我们在render内部称之为),并不精确(特别是后者),所以,如果我们在这里使用它们,我们有时仍然会过早地在外部循环中醒来,并花费一些时间“攻击”if,为了防止这种情况,我们仍然必须在else分支中插入sf::sleep
此外,我们必须在update的末尾调用ImGui::EndFrame,这样如果内部循环的几次迭代被执行,我们不会在没有ImGui::EndFrame的情况下最终调用ImGui::StartFrame(这将触发另一个Assert):

void update(sf::Time dt, sf::RenderWindow& window)
{
    // ...
    ImGui::EndFrame();
}


请注意,现在我们经常连续调用ImGui::EndFrame两次(内部循环的最后一次迭代中的update和后续的render),但这不是问题,因为由于这种检查,后续的ImGui::EndFrame调用在新帧开始之前没有效果。

相关问题