[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] experience with SDL2, event loop & main thread
From: |
Liviu Ionescu |
Subject: |
[Qemu-devel] experience with SDL2, event loop & main thread |
Date: |
Sat, 10 Dec 2016 18:16:20 +0200 |
given the reported problems with the SDL integration, here are some things I
learned while updating GNU ARM Eclipse QEMU to use SDL2 (SDL1 is already
issuing various warnings when running on macOS 10.12, and anyway it has several
limitations).
---
initially I expected that the project requires just a simple build script
update, but it was much more, I had to completely restructure the graphical
code.
in short, the main problem is the competition/contradiction in using the main
thread in QEMU.
by tradition, graphical systems and multi-theaded applications do not make good
friends, because graphical systems expect to run only on the application's main
thread.
SDL2 is not an exception; at least on macOS (Cocoa) and GNU/Linux (openGL), all
my attempts to run the graphical event loop on another thread failed with
horrible exceptions.
but running the event loop on the main thread is not enough, all graphical
primitives must be called from the main thread.
in QEMU this creates a problem, since normally graphic updates are triggered by
the emulated peripherals, which run on the specific emulator thread (for
example, in GNU ARM Eclipse QEMU case, for emulated LEDs, GPIO pin changes
alternate a rectangle on the board picture).
there might be specific primitives/configurations/platforms where this rule can
be relaxed, but the only portable, trouble free, solution is to run everything
on the main thread (SDL window creation, draw primitives, event loop, and
window destruction).
unfortunately this collides with QEMU use of the main thread to pump the I/O
event loop.
there are two possible solutions:
- move the qemu initialisations and I/O event loop on a new thread, and free
the main thread exclusively for the SDL event loop (which can now block waiting
for events)
- keep the qemu initialisations and the I/O event loop on the main thread and
schedule a timer every few milliseconds to poll if new graphical events are in
the queue.
the first solution is obviously the most efficient, since the graphical thread
does not consume resources when the there is no graphical activity, and
reaction speed to graphical updates is as good as possible. (unfortunately this
solution currently does not work on windows)
the second solution, which uses a timer to schedule a poll routine every few
milliseconds (for example every 10 ms), is much more inefficient, and adds a
small delay to all graphical displays. the timer routine is scheduled to run on
the main thread, so the condition to run the graphical event loop on the main
thread is satisfied.
fyi, this inefficient second solution, with a timer, is currently used by the
qemu graphical consoles.
to guarantee that all graphical primitives will be invoked only from the main
thread, calls should not be performed from the actual location (usually the
emulator tcg thread), but must be queued as user events and deferred to the
main thread, possibly using user events in the graphical event loop; for
example:
case SDL_USEREVENT:
// User events, enqueued with SDL_PushEvent().
switch (event->user.code) {
case GRAPHIC_EVENT_BOARD_INIT:
board_graphic_context = (BoardGraphicContext *) event->user.data1;
if (!cortexm_graphic_board_is_graphic_context_initialised(
board_graphic_context)) {
cortexm_graphic_board_init_graphic_context(
board_graphic_context);
}
break;
case GRAPHIC_EVENT_LED_INIT:
state = (GPIOLEDState *) event->user.data1;
if (!cortexm_graphic_led_is_graphic_context_initialised(
&(state->led_graphic_context))) {
cortexm_graphic_led_init_graphic_context(
state->board_graphic_context,
&(state->led_graphic_context), state->colour.red,
state->colour.green, state->colour.blue);
}
break;
case GRAPHIC_EVENT_LED_TURN:
state = (GPIOLEDState *) event->user.data1;
is_on = (bool) event->user.data2;
cortexm_graphic_led_turn(state->board_graphic_context,
&(state->led_graphic_context), is_on);
break;
// ...
default:
fprintf(stderr, "Unimplemented user event %d\n", event->user.code);
}
break;
special care must be observed for window destruction at program termination.
the SDL manuals recommend adding an atexit(SDL_Quit), but this isn't as simple
as it looks, because the SDL_Quit must be executed also on the main thread,
while the registered atexit() functions are executed on the context of the
thread that calls exit(). (trying to run SDL_Quit on another thread hangs on
Windows).
in practical terms, this means that exit() can be called only from the main
thread. calls from different threads should be replaced with code to push a
user event on the graphical loop, and perform the quit on the event loop.
one problematic such case is the semihosting exit. the semihosting code is
executed on the emulator thread, so it cannot directly call exit. enqueuing the
event is simple, but this code is executed with the io mutex locked, so the I/O
loop is not running, and the timer will not schedule graphical polls. my
workaround was to call `qemu_mutex_unlock_iothread()` to unlock the mutex, and
wait for the program to terminate. this might not be the right solution, but
seems functional.
---
conclusions:
- the graphical event loop and all graphical primitives must be called from the
main thread context
- in qemu this is not possible directly; an inefficient but functional solution
uses a timer programmed to call a function every few milliseconds, to poll the
event loop. (if the qemu I/O event loop is executed on the main thread, the
timer function will be called from the main thread context)
- all graphical operations initiated by the all other thread (like the emulator
thread), must be deferred (in SDL using user events) to the graphical event loop
- a function to run graphical destruction must be registered with atexit()
- only the main thread can call exit(), to ensure the destruction functions are
not called from other threads
- exiting from other threads must be also deferred to the main thread, possibly
with unlocking a mutex.
I hope that helps,
Liviu
- [Qemu-devel] experience with SDL2, event loop & main thread,
Liviu Ionescu <=
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Fam Zheng, 2016/12/11
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Liviu Ionescu, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Fam Zheng, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Liviu Ionescu, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Fam Zheng, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Liviu Ionescu, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Fam Zheng, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Daniel P. Berrange, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Liviu Ionescu, 2016/12/12
- Re: [Qemu-devel] experience with SDL2, event loop & main thread, Fam Zheng, 2016/12/12