I'm trying to make a project using the SFML and imgui-SFML. I'm new to imgui and not sure where exactly the problem is. From what I've gathered, the imgui::render has to happen in the same timestep as the imgui::update, which is limiting and suboptimal in the context of SFML and handling Delta time correctly.
Here's some minimal example of my problem:
#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();
}
If I remove the second while loop ( while (timeSinceLastUpdate > mTimePerFrame) ), everything works correctly, but that makes my game framerate dependent, which I obviously want to avoid.
Your error about new frame is triggered by this assert because
update
and thusImGui::SFML::Update
(which callsImGui::NewFrame
) isn't called before the firstrender
call (which eventually callsImGui::EndFrame
linked above). Even though you have a mechanism for callingupdate
with fixed time steps, the outer loop doesn't actually have any frame limiting mechanism, so what happens (or, rather, would happen if not for the assert failure) is that, on most iterations of the outer loop, inner loop isn't entered (timeSinceLastUpdate
hasn't accumulated yet), butrender
gets called.An easy way to fix this is to assure that
render
gets called only when there was an update of the game state (i.e. inner loop had at least one iteration) on the current outer loop iteration. We can do this by transforming the inner loop fromwhile
toif-do-while
and movingrender(window);
insideif
. Not only this solves theImGui::NewFrame
problem (since now by the timerender
executes it has already been called), but saves from unneccessary rendering/drawing non-updated game state.But, now game (outer) loop just keeps iterating and unnecessarily holding its thread busy while checking time every few microseconds (if not more often) before
timeSinceLastUpdate
eventually surpasses your fixed time threshold. Instead, we can put our thread to sleep for roughly the time needed before we can enter theif
, which can be done e.g. inelse
branch:Note that in SFML frame limiting is often done not by
sf::sleep
, but bysf::Window::setVerticalSyncEnabled
(VSync implemented on GPU driver level) orsf::Window::setFramerateLimit
(which usessf::sleep
andsf::Window
's own internal clock), where the latter is particilarly similar to what we did. But both of these functions block insidesf::Window::display
(which we call insiderender
) and are not exact (especially the latter), so, if we used them here, we could sometimes still wake up in the outer loop too early and spend a bit of time 'attacking' theif
, to prevent which we would still have tosf::sleep
insideelse
branch.Also we have to call
ImGui::EndFrame
at the end ofupdate
so that if several iterations of the inner loop are executed we don't end up callingImGui::StartFrame
consequtively withoutImGui::EndFrame
in between (that would trigger another assert):Note that now we frequently call
ImGui::EndFrame
two times in a row (theupdate
in the last iteration of inner loop and subsequentrender
), but this isn't a problem since due to this check subsequentImGui::EndFrame
calls have no effect until new frame is started.