Search code examples
c++sfmlimgui

Forgot to call ImGui::NewFrame()?


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.


Solution

  • 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.