0
\$\begingroup\$

When I wrote my first application in SDL, it looked like this:

while (!quit)
{
    SDL_PollEvent(&event);

    switch (event)
    {
         // ...
    }
}

But then one time I left my app running while I went to do something else, and when I came back my laptop was boiling hot. I checked the system monitor and one of my cores was maxed out by my program! So I did some research, and now my loop looks like this:

while (!quit)
{
    SDL_WaitEvent(&event);

    switch (event)
    {
         // ...
    }
}

Great, so now my program doesn't max the CPU. But now I need to introduce rendering, and the problem is, currently this loop runs one iteration per event. If input and other events don't occur, my program won't render the next frame, no matter where the rendering code goes in the loop. So that's no good.

I could have threads, but that seems like a lot of extra complexity when I could just do:

while (!quit)
{
    SDL_PollEvent(&event);

    switch (event)
    {
         // ...
    }

    SDL_Delay(10)
}

This won't melt the CPU, and I can render at about 100 frames per second.

My questions are:

  1. Which approach "should" I use? I don't see any reason to not just use (3).

  2. What's the point of SDL_WaitEvent, if you can't use it the moment you want to do anything other than handle events in your game loop?

  3. I assume SDL_WaitEvent does something like approach (3) under the hood, so even though ostensibly approach (3) should introduce more latency, in practice it won't. Is this correct? How low should the integer passed to SDL_Delay be to get a responsive app?

\$\endgroup\$

4 Answers 4

3
\$\begingroup\$

That's an interesting problem, I handle my SDL events like this,

while (SDL_PollEvent(&e) != 0) 
{
    // handle events here
}

This will handle all the SDL events in the queue, and if their aren't any events in the queue it just ignores trying to handle the events in the queue.

P.S. at the end of each iteration of my game loop (The handle user input, update game world, render world) I just delay the loop so that it won't iterate more than 60 times a second.

// Timing variables
const int FPS = 60;
const int frameDelay = 1000 / FPS;
Uint32 frameStart;
int frameTime;

// Game Loop
while(playing)
{
    // Stores the number of ticks at the start of the loop
    frameStart = SDL_GetTicks();
    
    // HandleEvents
    // Update World
    // Render World

    // This measures how long this iteration of the loop took
    frameTime = SDL_GetTicks() - frameStart;

    // This keeps us from displaying more frames than 60/Second
    if(frameDelay > frameTime)
    {
        SDL_Delay(frameDelay - frameTime);
    }
}
    
     
\$\endgroup\$
1
\$\begingroup\$

As you pointed out, SDL_WaitEvent is not suitable if you need to draw even if there are no input events. That is not desirable for games. But it could be useful, for example, if you have an application that only does UI, because you only need to refresh when the user interacts with the UI.

For games, very often we use a technique called VSync. This technique will make your loop refresh at the same rate as your monitor. So if your monitor has a refresh rate of 60Hz, the loop will match that. Additionally, it will prevent screen tearing artifacts.

In SDL you can enable VSync in different ways depending on what you are doing:

  1. If you are using SDL's renderers, you can use the SDL_RENDERER_PRESENTVSYNC flag.
  2. If you are using OpenGL you can call SDL_GL_SetSwapInterval(1).

Alternativelly, you can use SDL_Delay. But, instead of waiting a fixed amount of time, you should wait depending on how much time you have spent doing computations, in order to keep a constant frame time. This approach is what @sketBURD 's answer is about.

\$\endgroup\$
0
\$\begingroup\$

LazyFoo's Tutorials use SDL_PollEvent in within it's own loop inside the main loop, to ensure all events are handled and not only the 1st one.

The main loop would look like this:

//Main loop flag
bool quit = false;

//Event handler
SDL_Event e;

//While application is running
while( !quit )
{
    //Handle events on queue
    while( SDL_PollEvent( &e ) != 0 )
    {
        //User requests quit
        if( e.type == SDL_QUIT )
        {
            quit = true;
        }
    }

    // everything else
}

You might also like to take a look at the Calculating framerate and Capping Framerate tutorials.

\$\endgroup\$
0
\$\begingroup\$

One idea is that you can use the SDL_WaitEvent loop, so that the thread blocks and yields the CPU (and can allow the CPU to idle and cool down).

But what you can do is create your own user-defined event - let's call it "SDL_ANIMATEEVENT" (but you'd use SDL_RegisterEvents to grab an available event ID for it) - and then have the thread post that to its own queue.

So, for example, the user clicks on a button. This wakes the thread with a mouse event. That mouse event then starts an animation - the button graphic might animate inwards and outwards to make it look like it's being pressed down and released, or you might have it smoothly change colours on hover - and then it posts the "SDL_ANIMATEEVENT" to its own message queue.

The trick being that when the program returns to SDL_WaitEvent, the "SDL_ANIMATEEVENT" message is in the queue, so the call doesn't block, the message gets picked up and we go through the loop one more time.

The processing for "SDL_ANIMATEEVENT" can move the animation forward by one frame and, if the animation isn't complete, it posts another "SDL_ANIMATEEVENT" to its own message queue.

So, again, when it reaches SDL_WaitEvent, it doesn't block, as there's a message waiting for it. And we do one more rendering loop.

Eventually, the animation is complete, so the processing for "SDL_ANIMATEEVENT" does not put another "SDL_ANIMATEEVENT" message on the queue. And when we reach SDL_WaitEvent again, there aren't any messages waiting for it and it actually now blocks waiting for an event.

This way of doing things is good for more "burst-y" animation. Where you're not always animating every single frame - but you might need to "burst" a few frames of animation, before going back to blocking again.

Perhaps less useful for games, where you're animating every single frame all the time (though this scheme could work for that too, but if you're always animating every single frame then it's just overhead, because the branch is always taken) but for GUIs, say, this could be exactly what you need.

When there's truly nothing to be done, you can block on SDL_WaitEvent. But if you need it to behave like SDL_PollEvent then keep posting that "SDL_ANIMATEVENT" onto the message queue, which stops SDL_WaitEvent blocking, and then you can stop posting "SDL_ANIMATEEVENT" when you want to go back to blocking again.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .