Drawing from different threads

Hello,

Recently Micah wrote about his ‘graphics server for threaded apps’.
The discussion went to several issues afterwards but not to the central
problem: how does a multi-threaded app do its drawing from different
threads? In the following I would like to share my solution.
I’m supposing that drawing only can be done in one thread, the main
thread. Also I only want to use SDL library functions.

The first thing you need is a queue for drawing tasks, that can be
filled by different threads, after being locked by a mutex. A thread
first sets the value of relevant variables (maybe contained in a
scene graph). The main loop of your program checks whether
something is in the queue, and if yes then performs the drawing task.

If no then SDL_PollEvent() can check whether e.g. the mouse was
clicked. If there was an event, it should be handled as usual.

Now comes the tricky part: what must be done if there is nothing
left in the queue and there are also no events? Two trivial solutions:

  • Jump right back to the start of the loop. The result will be
    that the CPU is occupied 100%, thus hampering other applications
    and exhausting your batteries.
  • Use SDL_Delay(), but which delay value must be choosen? A low value
    together with a slow CPU will behave like solution one.
    A higher value plus a fast CPU will create the danger that the
    queue grows too big.

At this point SDL_CondWait() comes to the rescue. This function
is a wrapper around the Unix system call pthread_cond_wait() which
takes 2 parameters: a condition and a mutex. The function blocks
until somebody has ‘signalled’ the condition. Who will do the
signalling? The obvious place for this is the function that places
drawing tasks into the queue. What if no thread is doing that? We use
one extra thread that adds an item to the queue at a regular interval
(e.g. 50 mS). This interval will translate to the maximum latency that
the user perceives when he clicks the mouse. All subsequent user events
will be executed without any delay, which also counts for the drawing
commands from the other threads, until the idle state is reached again.

Some code snippets to clarify things, supposing that you have available:

  • An object called ‘queue’ with methods push(), pop() and is_empty().
  • A function handle_user_event() to process drawing tasks.
  • A function handle_events() to process SDL_Event’s.***************************************************

    SDL_mutex *mtx=SDL_CreateMutex();
    SDL_cond *cond=SDL_CreateCond();
    bool quit;

// placing tasks in the queue
void send_user_event(int cmd) {
SDL_mutexP(mtx);
queue.push(cmd);
SDL_CondSignal(cond);
SDL_mutexV(mtx);
}

// keap-alive thread function
int keep_alivefun(void* data) {
while (!quit) {
SDL_Delay(50);
send_user_event(‘go’);
}
return 0;
}

int main() {

SDL_Event ev;
SDL_CreateThread(keep_alivefun,0);
while (!quit) {
while (true) { // items in the queue?
SDL_mutexP(mtx);
if (queue.is_empty()) {
SDL_mutexV(mtx);
break;
}
int task;
uev_queue.pop(&task);
SDL_mutexV(mtx);
if (task!=‘go’)
handle_user_event(task);
}
if (SDL_PollEvent(&ev)) { // any SDL_Event’s?
if (ev.type==SDL_QUIT) {
quit=true; break;
}
handle_events(&ev);
}
else {
SDL_mutexP(mtx);
SDL_CondWait(cond,mtx);
SDL_mutexV(mtx);
}
}
SDL_Quit();
}


Anybody a better or simpler idea?

Kind regards,
Wouter

W.Boeke <w.boeke chello.nl> writes:

Hello,

Recently Micah wrote about his ‘graphics server for threaded apps’.
The discussion went to several issues afterwards but not to the central
problem: how does a multi-threaded app do its drawing from different
threads? In the following I would like to share my solution.
I’m supposing that drawing only can be done in one thread, the main
thread. Also I only want to use SDL library functions.
<>
Anybody a better or simpler idea?

Kind regards,
Wouter

Looks good to me! One thing I was hoping to have was the ability to access
events in all the threads, so you could have a dedicated GUI thread. But I
found a few issues with the SDL event system. You can use PumpEvents, and
PeekEvents. But if you use PeekEvents to just peek and see if an event is in
there you need to deal with, it doesn’t get removed from the event queue.
PumpEvents doesn’t clear the queue apparently. So in my tests I had to have a
loop using PollEvent. That has the potential of flushing events before the
threads could access them.

One solution to this would be for SDL’s event queue to have an Event ID that you
can pass to remove from the queue if it has been handled. The idea could simply
be a millisec variable as to when the event was received.
SDL_RemoveEvent(event->ID)

But a better way would be to tack onto the event system. The main thread polls
events and pushes them into another queue that is shared with other threads. It
includes the millisec ID as stated above. But then the main thread checks the
age of each event and removes them (say after one second) assuming they are not
needed.

BUT, as the other threads on this subject pointed out, what is the point? Would
you really benefit from this? But it is fun to work on!

  • Micah

Micah Brening wrote:

Looks good to me! One thing I was hoping to have was the ability to access
events in all the threads, so you could have a dedicated GUI thread. But I

Dear Micah,

You should not want a thing like this! The complete event queue plus the
event handling are meant to be living in only one thread, the main thread.
So another thread should never try to place events in the event queue.
The only possibility is that a thread sets all the variables that it wants to,
and then asks the main thread to do the drawing. Believe me, it is really
possible to create very complicated applications this way.

Wouter

W.Boeke <w.boeke chello.nl> writes:

Dear Micah,

You should not want a thing like this! The complete event queue plus the
event handling are meant to be living in only one thread, the main thread.
So another thread should never try to place events in the event queue.
The only possibility is that a thread sets all the variables that it wants to,
and then asks the main thread to do the drawing. Believe me, it is really
possible to create very complicated applications this way.

Wouter

Wouter,

I just like to see if it is possible. I seriously doubt there would ever be a
reason to do it, but I just like to see if it is possible to do in the first
place. And if it isn’t, then see if there is a way to make it possible.

But with most video games, unless it is a commercial 3D game, there isn’t much
need for threading, right? No real benefit to it for small scale games. Or am
I wrong?

  • Micah

Micah Brening wrote:

But with most video games, unless it is a commercial 3D game, there isn’t much
need for threading, right? No real benefit to it for small scale games. Or am
I wrong?

The short answer is “yes”. What is the fun of a game if there is no sound?
This only can be generated in a separate thread (or in a separate process,
however that would complicate things a lot).

Wouter