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 thus ImGui::SFML::Update
(which calls ImGui::NewFrame
) isn't called before the first render
call (which eventually calls ImGui::EndFrame
linked above). Even though you have a mechanism for calling update
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), but render
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 from while
to if-do-while
and moving render(window);
inside if
. Not only this solves the ImGui::NewFrame
problem (since now by the time render
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 the if
, which can be done e.g. in else
branch:
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 */);
}
}
Note that in SFML frame limiting is often done not by sf::sleep
, but by sf::Window::setVerticalSyncEnabled
(VSync implemented on GPU driver level) or sf::Window::setFramerateLimit
(which uses sf::sleep
and sf::Window
's own internal clock), where the latter is particilarly similar to what we did. But both of these functions block inside sf::Window::display
(which we call inside render
) 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' the if
, to prevent which we would still have to sf::sleep
inside else
branch.
Also we have to call ImGui::EndFrame
at the end of update
so that if several iterations of the inner loop are executed we don't end up calling ImGui::StartFrame
consequtively without ImGui::EndFrame
in between (that would trigger another assert):
void update(sf::Time dt, sf::RenderWindow& window)
{
// ...
ImGui::EndFrame();
}
Note that now we frequently call ImGui::EndFrame
two times in a row (the update
in the last iteration of inner loop and subsequent render
), but this isn't a problem since due to this check subsequent ImGui::EndFrame
calls have no effect until new frame is started.