我正在尝试使用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)),一切都正常工作,但这会使我的游戏依赖于帧率,这显然是我想避免的。
1条答案
按热度按时间3zwtqj6y1#
这个Assert触发了关于新帧的错误,因为
update
和ImGui::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
分支中完成:字符串
请注意,在SFML中,帧限制通常不是由
sf::sleep
完成,而是由sf::Window::setVerticalSyncEnabled
完成(在GPU驱动程序级别实现VSync)或sf::Window::setFramerateLimit
(它使用sf::sleep
和sf::Window
自己的内部时钟),其中后者与我们所做的特别相似。但是这两个函数都在sf::Window::display
内部阻塞。(我们在render
内部称之为),并不精确(特别是后者),所以,如果我们在这里使用它们,我们有时仍然会过早地在外部循环中醒来,并花费一些时间“攻击”if
,为了防止这种情况,我们仍然必须在else
分支中插入sf::sleep
。此外,我们必须在
update
的末尾调用ImGui::EndFrame
,这样如果内部循环的几次迭代被执行,我们不会在没有ImGui::EndFrame
的情况下最终调用ImGui::StartFrame
(这将触发另一个Assert):型
请注意,现在我们经常连续调用
ImGui::EndFrame
两次(内部循环的最后一次迭代中的update
和后续的render
),但这不是问题,因为由于这种检查,后续的ImGui::EndFrame
调用在新帧开始之前没有效果。