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
updateand thusImGui::SFML::Update(which callsImGui::NewFrame) isn't called before the firstrendercall (which eventually callsImGui::EndFramelinked above). Even though you have a mechanism for callingupdatewith 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 (timeSinceLastUpdatehasn't accumulated yet), butrendergets called.An easy way to fix this is to assure that
rendergets 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 fromwhiletoif-do-whileand movingrender(window);insideif. Not only this solves theImGui::NewFrameproblem (since now by the timerenderexecutes 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
timeSinceLastUpdateeventually 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. inelsebranch: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::sleepandsf::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::sleepinsideelsebranch.Also we have to call
ImGui::EndFrameat the end ofupdateso that if several iterations of the inner loop are executed we don't end up callingImGui::StartFrameconsequtively withoutImGui::EndFramein between (that would trigger another assert):Note that now we frequently call
ImGui::EndFrametwo times in a row (theupdatein the last iteration of inner loop and subsequentrender), but this isn't a problem since due to this check subsequentImGui::EndFramecalls have no effect until new frame is started.