When using the X shared memory extension, the X server may start
reading the buffer at a time when the client is writing to it. This
can result in the next unfinished frame flashing onto the screen. The
canonical way to solve this is to wait for an event to come in
signaling that it's safe to continue, but that would add considerable
latency to rendering. Instead, we bet on the fact that it won't take
all that long, and so we double-buffer. We have two buffers shared
with the X server and swap them at the end of each frame. We only draw
to the one not being displayed. That way the X server should always
have time to read out a full finished frame.
So our workaround for making usleep thread-safe mostly worked in that
it fixed one of the things making it thread-unsafe, but it seems that
sigsuspend, which usleep uses, is also not thread-safe, since
sometimes one of our two threads would freeze, stuck inside
sigsuspend. So instead of usleep, we're using a trick that Stacken
member map suggested: using select. Calling select with no file
descriptors to work on will have it block on the kernel level until
its timeout is hit. This acts as a sleep. As it turns out, this seems
to completely solve our sleep-related problems, and massively reduces
the amount of audio buffer underruns as a nice bonus!
This is apparently necessary, and I thought it was leading to the main
game thread freezing, but after implementing and testing, that does
not appear to be the case. Still, implementing these is correct, so
it's best to have it done anyway.
Uses the audio device's "EOF counter" to tell how many audio buffers
we've played, then ensures that production is three buffers ahead of
playback. This has been tested and works. Either game or audio thread
may still freeze, this can likely be fixed by implementing the locks.
On Solaris, usleep is not thread-safe and will introduce a race
condition when used in a multithreaded environment. This manifests as
SIGALRM signals escaping from its internal call to sigsuspend. If a
signal is missing a handler, the process dies upon receiving it,
crashing.
The Solaris libc code does something rougly equivalent to this:
PreserveSignalHandlers(...);
/* Effectively NOPs out SIGALRM */
DisableSIGALRM(...);
SetTimer(...);
/* Suspends the thread until it receives a SIGALRM signal. When it
receives this signal, it both wakes up *and* receives the
signal. In this case we don't want to do anything but wake up,
so the SIGALRM handler is a NOP.
*/
sigsuspend(...);
RestoreSignalHandlers(...);
return;
The consequences of this code in a multithreaded context is that if
two sleeps are in progress at the same time, one will necessarily
finish before the other, *restore the non-existant handlers*, and go
on doing its thing. When the other sleep finishes, it won't have a NOP
handler to call when waking up, a handler is searched for, none is
found, and thus we crash.
The game appears to render level tiles as 16x16 pixel tiles, and many
of these do not require any transparency. So we now compute which
tiles are completely opaque and store those in a set, so that when we
draw them we can skip color keying.
Declaring a "register variable" isn't allowed in modern C++ so I ditch
that in the path that my dev machine compiles.
I *should* remove them from both and update GCC on the Sun to as new
as possible, but I'm not going to do that because that sound painful,
so this is what it is.
The main justification for this is that normal behaviour from a
backend is to have the main thread be interrupted by the audio system
and to then stay within the audio system while getting audio
frames. Parking the main thread simulates this by ensuring nothing
else happens in the game's logical flow at the same time.