SDL event loop CPU usage

Hello,

Is it possible to organize event loop so that it consumes 0% CPU when there are no events?
Currently I’m doing it like this:

#include <SDL2/SDL.h>

#define WIN_H 300
#define WIN_W 400

enum error {
    EINIT = 1,
    EWINDOW,
    ERENDER,
};

int main(int argc, char* argv[]) {
    SDL_Window*   window   = NULL;
    SDL_Renderer* renderer = NULL;

    int status = 0;

    do {
        if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
            status = EINIT;
            break;
        }

        window = SDL_CreateWindow(
            "Demo EventLoop",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            WIN_W,
            WIN_H,
            SDL_WINDOW_RESIZABLE);

        if (!window) {
            status = EWINDOW;
            break;
        }

        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

        if (!renderer) {
            status = ERENDER;
            break;
        }

        SDL_Event e;
        while (SDL_WaitEvent(&e)) {
            if (e.type == SDL_QUIT) {
                break;
            }
            SDL_Delay(50);
        }
    } while (0);

    if (renderer) {
        SDL_DestroyRenderer(renderer);
    }

    if (window) {
        SDL_DestroyWindow(window);
    }

    SDL_Quit();

    return status;
}

This demo application consumes 1.8-2.0% CPU when there are no events. Seems like I’m doing something wrong. I would be very appreciated for advice about how to do the event loop right.

Vladimir

If you do not create games, I think you should use a system API like WhiteForSingleObject(s) for Windows
And make your own event handler.

FIX: WaitForSingleObject(s)

PS: In your separete thread window handler

That doesn’t seem wrong to me. SDL_WaitEvent still needs to do some work to actually check if there are events. Maybe there are some optimizations you could make if you made a custom event solution like ANTA suggested, but you’ll need to evaluate if the effort of making the custom solution is worth 1-2% CPU usage.

Hello, thank You for the reply!
I’m creating a terminal emulator (just for fun) for UNIX (OS X/Linux) and I tried to use SDL_CondWait (with SDL_CondSignal in SDL_AddEventWatch) in main loop, but in that case application is displayed as “not responding”.

I think 1-2% CPU is high consumption for an idle application. On laptops it can cause additional battery usage. As I could see in Activity Monitor, normally an idle application consumes 0% CPU.

SDL_WaitEvent polls for events in a loop with SDL_Delay(10) in between to keep cpu usage down. It doesn’t actually make your app go idle and use something like a wait handle. So that explains the cpu usage.

Thank You!
Yes, it explains. But is it possible to reduce CPU usage? Additional SDL_Delay reduces CPU usage, bot not to 0 and application becomes not so responsive. Solution with wait/signal a condition variable requires additional initialized thread which will listen for events actually by the same manner like in the main loop, so it seems like I will get 1-2% CPU usage, not from main thread but from a background thread. I noticed I nice functions SDL_AddEventWatch/SDL_SetEventFilter but seems like they are called from the same thread (at least if the following code does not contain a critical error):

#include <stdio.h>

#include <SDL2/SDL.h>

#define WIN_H 300
#define WIN_W 400

enum error {
    EINIT = 1,
    EWINDOW,
    ERENDER,
    EMUTEX,
    ECOND,
};

typedef struct {
    SDL_mutex* mutex;
    SDL_cond*  cond;
} condition_t;

int onevent(void* userdata, SDL_Event* event) {
    condition_t* c = (condition_t*)userdata;
    if (c) {
        printf("[%ld] %s\n", SDL_ThreadID(), "signaling event ...");
        SDL_LockMutex(c->mutex);
        SDL_CondSignal(c->cond);
        SDL_UnlockMutex(c->mutex);
        printf("[%ld] %s\n", SDL_ThreadID(), "signaling event ok");
    }
    return 1;
}

void render(SDL_Renderer* renderer, SDL_Surface* screen) {
    SDL_Texture* scene = SDL_CreateTextureFromSurface(renderer, screen);

    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, scene, NULL, NULL);
    SDL_RenderPresent(renderer);

    SDL_DestroyTexture(scene);
}

int main(int argc, char* argv[]) {
    SDL_Window*   window   = NULL;
    SDL_Renderer* renderer = NULL;
    SDL_Surface*  screen   = NULL;
    SDL_mutex*    mutex    = NULL;
    SDL_cond*     cond     = NULL;

    int status = 0;

    do {
        if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
            status = EINIT;
            break;
        }

        window = SDL_CreateWindow(
            "Demo EventLoop",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            WIN_W,
            WIN_H,
            SDL_WINDOW_RESIZABLE);

        if (!window) {
            status = EWINDOW;
            break;
        }

        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

        if (!renderer) {
            status = ERENDER;
            break;
        }

        screen = SDL_CreateRGBSurface(0, WIN_W, WIN_H, 32, 0, 0, 0, 0);
        if (!screen) {
            break;
        }

        SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));

        mutex = SDL_CreateMutex();
        if (!mutex) {
            status = EMUTEX;
            break;
        }

        cond = SDL_CreateCond();
        if (!cond) {
            status = ECOND;
            break;
        }

        condition_t c = { .mutex = mutex, cond = cond };
        // SDL_SetEventFilter(onevent, &c);
        SDL_AddEventWatch(onevent, &c);

        SDL_LockMutex(mutex);

        render(renderer, screen);
        while (1) {

            SDL_Event e;
            if (SDL_PollEvent(&e)) {
                if (e.type == SDL_QUIT) {
                    break;
                }
            } else {
                printf("[%ld] %s\n", SDL_ThreadID(), "waiting for event ...");
                SDL_CondWaitTimeout(cond, mutex, 100);
                printf("[%ld] %s\n", SDL_ThreadID(), "waiting for event ok");
            }

            render(renderer, screen);
        }
        SDL_UnlockMutex(mutex);

        // SDL_Event e;
        // while (SDL_WaitEvent(&e)) {
        //     if (e.type == SDL_QUIT) {
        //         break;
        //     }
        //     SDL_Delay(50);
        // }
    } while (0);

    if (cond) {
        SDL_DestroyCond(cond);
    }

    if (mutex) {
        SDL_DestroyMutex(mutex);
    }

    if (screen) {
        SDL_FreeSurface(screen);
    }

    if (renderer) {
        SDL_DestroyRenderer(renderer);
    }

    if (window) {
        SDL_DestroyWindow(window);
    }

    SDL_Quit();

    return status;
}

I dont think you’ll ever get CPU down to 0. Can I ask? The do while, what is the purpose?

Purpose is because you have a UI only app and users don’t want it burning CPU cycles while not being used.

My apologies, I don’t know how to format code, first time posting some:
Edit: thank you SJR!

#include <SDL2/SDL.h>

int main()
{
        SDL_Init(SDL_INIT_EVERYTHING);
        SDL_Window * win = SDL_CreateWindow("Lower CPU", 10, 10, 400, 400, SDL_WINDOW_SHOWN);
        int count = 0;
        bool run = true;
        while(run)
        {
                SDL_Event ev;
here:
                if(SDL_WaitEvent(&ev))
                {
                        switch(ev.type)
                        {
                                case SDL_QUIT:
                                         run = false;
                                        break;
                        }
                }
                else
                {
                        goto here;
                }
                count ++;
                SDL_Log("Loop %i, event->type: %i", count, ev.type);
        }
        SDL_DestroyWindow(win);
        SDL_Quit();
}

I know I did this once without the goto statement, but I don’t remember the logic that I used previously.

If you look at the source code, SDL_WaitEvent() actually calls SDL_WaitEventTimeoutNS(event, -1).
The timeout function then activates a wait device. This device can and does return even though it was told to wait forever. When a bad wait-return occurs (which in real life happens once every 1-5 seconds or so) then the function returns 0 as a result. If you don’t catch that, then a whole frame of data will have to be processed which is generally enough to spin up your CPU to a more active state. By using the goto we sacrifice just enough CPU to jump back to the SDL_WaitEvent (very nearly 0% cpu).

Anyhow, try out the above code, I’ll try to remember the version without a goto.

You can start a formatted code block with 3 backticks, then end it with 3 more

Instead of the goto, perhaps try a while loop and SDL_PollEvent():

while(run) {
    // According to the docs, passing NULL leaves the event on the queue
    if(SDL_WaitEvent(NULL)) {
        // while loop in case multiple events come in at once
        SDL_Event event;
        while(SDL_PollEvent(&event)) {
            switch(event.type) {
            case SDL_QUIT:
                run = 0;
                break;
            }
        }
        // do other frame stuff
    }
}