












			ON PORTING MAELSTROM TO LINUX


			by
			  Sam Lantinga





Graphics:

	X11 was originally designed as a well defined client-server
protocol, device independent, network capable windowing system.  It was
fully capable of supporting the applications of the time: menus, text
editors, simple bitmap graphics, etc.  However, many applications today
require a faster, more responsive graphics interface.  Any real-time video
application requires much faster video access and updates than the X11
system was originally designed to provide.  Examples of these types of
applications include teleconferencing systems, MPEG video players, real-time
modeling object modeling, etc.  The pioneers in real-time audio/visual
techniques have been (and probably will be) fast-action video games.  Arcade
games stress hardware more than almost any other application, requiring
real-time video, real-time audio, and good response time to user input.
More advanced games run networked as well, requiring fast, well designed
transmission control protocol and routing of network packets (not discussed
in this paper.)

	These real-time services are easy to provide in a simple,
single-user system such as the DOS PC, making it one of the most popular
platforms for video games.  It is slightly more complex to provide these in
a well designed single-user windowing system such as the Macintosh. 
However, even there, it is possible to give full access to the hardware to
any applications that require it.  This performance is much harder to
provide in a multi-user time-sharing environment such as UNIX X11.


	The objective of this project was to show that fast-action games,
and hence, other high-speed interactive applications, can be written for the
X11 environment.  The target application for this project was the Macintosh
shareware game "Maelstrom".  From the press release:

	"Maelstrom 1.4 is a marriage of the venerable Asteroids concept 
	 with new digitized sound effects, 3D graphics and high resolution
         256 color animation.  'With fantastic art and graphics by Ian 
	 Gilman and Mark Lewis, Maelstrom does for the tired old Asteroids-
	 style games what the Mazda Miata did for sporty roadsters...'"

As such, Maelstrom seemed the perfect target for the X11 environment.
The operating system chosen for the port was Linux version 1.2.9, a free
UNIX-like operating system for the PC.  The development hardware was a
DX2/66 VLB PC with a Cirrus Logic 5426 video board and a Sound-Blaster Pro
8-bit sound-card.  The X11 environment used was XFree86 version 3.1, using
the SVGA X server with the built in MITSHM extension.

	After obtaining the source code through a non-disclosure agreement
with the author, Andrew Welch, I began the port.  The most immediate
problems I ran into had nothing to do with graphics speed, or hardware
access.  How was I going to be able to get the icons, sprites and sounds out
of the Macintosh resource fork files?  Throughout the port, whenever I would
run into these problems, there would be some resource, some paper or program
written that would help me solve the problem.  A (nearly) complete list of
these and credits are in an appendix to this paper.  For the specs on the
Macintosh resource files, I went to the well done series "Inside Macintosh",
published by Addison and Wesley.  With the specs in hand, I coded C++
classes that could parse Macintosh resource forks (e.g. dialog box
specifications, custom fonts, sprites, icons, sound clips, etc.) directly
from the UNIX file-system.  How was I able to get the resource forks there
in the first place?  That was made possible by the new HFS file-system
driver for Linux, written by Paul Hargrove.  Once I had routines that could
extract sprites, color icons, and color-maps, I went to work on the graphics
interface.

	I had a game for X11, called "Xboing" written by an Australian
Justin Kibell, which used the Xpm library for full color animation in a
well designed blockout-style game.  (For more information, his WWW page is
http://144.110.160.105:9000/~jck/xboing/xboing.html)  However, using the
Xpm library, copying pixmap sprites from application to X server, his
animation has a great deal of "flicker" where the animation seems to flash
as it goes along.  His method of dealing with sounds (designed for the 
sun /dev/audio interface) is not tightly synchronized, leading to sounds
and animation for a single event occurring several seconds apart.  The effect
is similar to that of a high flying jet -- the sight of the jet precedes
the sound of the jet by several seconds.

	Another game, recently ported to the Linux X11 environment, is
the popular "DOOM!" game, by ID Software.  DOOM! uses frame-buffer based
animation, where multiple frames are blitted to the screen per second,
giving the appearance of smoothly flowing graphics.  The X11 port takes
advantage of an extension to the X11 protocol known as "MITSHM", or 
"MIT Shared Memory Extension" in which the client application and the X
server share a segment of memory that corresponds to an off-screen "image".
Graphics are drawn to this off-screen image and a single call to the X11
server tells it to copy the image to a window on the physical screen.
This is much faster than sending each frame through the client-server
communications connection.  For a 320x200 resolution window, this results
in reasonable performance.  The game can be played smoothly and in this
game, sound is tightly coupled to the actions in the game, for realistic
play.

	In all but the fastest systems, the MITSHM off-screen image
technique is limited to 320x200 resolution.  Any larger, and the X server
cannot copy the image to the window fast enough for smoothly flowing
graphic frames.  Most X systems run in higher resolution, turning the
full-screen virtual reality game "DOOM!" into a tiny window on the desktop. 
Maelstrom was designed for 640x480 resolution, and uses moving sprite
animation, so the MITSHM off-screen image technique is not suitable for the
Maelstrom port.  The Xpm method of erasing and drawing sprite images would
be perfect for Maelstrom, if the images could be copied fast enough to
provide smoothly moving animation.  With sometimes upwards of 30 moving
objects on the screen at once, the copying would have to be fast indeed.

	I combined both approaches.  Using a technique I haven't heard of
before, I used the feature that a pixmap can be used as a window background,
in combination with the fact that a shared memory segment can be associated
with a pixmap, to create a shared memory pixmap that was the background of
a window!  I can then directly manipulate the pixels in the background and
simply call XClearArea() to refresh the changed area of the screen.  This
technique gives an incredible speedup over the shared memory image method,
because the window is not modified by copying an entire frame, but by merely
refreshing the existing one.  All graphics drawing is done on the background
of the window and then placed on the display by calling XClearArea().  This
technique completely breaks down the client-server network relationship of
the X11 environment, but for certain high-speed graphics applications, the
speed increase is definitely worthwhile.

	Even with the speedy technique of shared background pixmaps, there
needs to be some way of copying the original sprite pixel data into the
shared background (hereafter referred to as the frame-buffer.) A technique
suggested by Andrew Welch, was to compile the sprites into streams of pixels
and opcodes.  Instead of running a loop, copying each pixel independently
after checking to see if it needs to be put on the screen, compiled sprites
are streamed onto the frame-buffer using the largest copy possible, switching
on the next opcode to see how big a skip to make, streaming a next copy,
skipping, etc.  Copying compiled sprites was 50 percent faster than using
the old sprite/mask technique. An additional advantage was that compiled
sprites took an average of 50 percent less storage space than the old
sprites and masks.  Compiled sprites ran into problems however, when
clipping them on the edge of the screen.  sprite pixel-maps and masks have
the advantage that each row of pixels is a fixed width, and all you have to
do is skip a certain amount into each row to clip an edge of the sprite. 
Each row of a compiled sprite is variable width, and the checking required
to clip them would take longer than the pixel/mask method.  I wrote a
routine to dynamically recompile a compiled sprite, based on clipping
conditions, but it too was slightly slower than the pixel/mask method.  For
sprites with absolute coordinate positions, I use the pixel/mask image
updating during clipping and for small sprites with relative coordinate
positions (thrust sprites, etc) I use compiled sprite recompiling during
clipping.  This combination of techniques resulted in a good graphics speed
for high-speed play of the game Maelstrom.

	Color mapping is a big issue in the X11 environment.  Since the
screen is shared with other applications, and there are limited color
entries in the color table, you need some way of getting the colors you
need, without depriving other applications of the colors they need.  There
are several ways to approach this.  "Netscape", a world wide web browser,
just grabs all the free color cells and allocates the colors it wants. 
"xv", an image viewer, allocates an orthogonal spectrum of color cells and
maps the colors it wants to this spectrum of colors.  "DOOM!" allocates a
private color-map, fills it with it's own colors and uses that.  This has the
side-effect of turning all other windows on the X display into psychedelic
color friezes.  Since Maelstrom uses a full 256 color table, it needs more
colors than are generally free in a shared color-map.  The approach I took
was similar to that of xv -- I allocated a spectrum of colors, and then 
mapped the colors I wanted to this spectrum, plus the colors already in 
the colormap.  A command-line option can be used to tell Maelstrom to
allocate a private color-map for "true" colors, the same way "DOOM!" does.

	Color mapping is only an issue in 8-bit displays.  Truecolor
displays can display thousands or millions of colors simultaneously and
require different color techniques.  My hardware will only support 8-bit
color, and so Maelstrom doesn't currently support Truecolor displays.

	The VGA port posed an interesting problem, compared to X11
graphics.  In the X11 paradigm, graphic updates are queued until explicitly
flushed, or until the next call for input.  Under SVGAlib, as soon as a
graphics request is made, the screen is updated.  This results in "flicker",
as all of the sprites are erased, then moved, and then they are updated.
My solution was to create an expandable stack of refresh updates, and then
only update the screen when explicitly told to, or when asked for input.
This gets rid of the flicker quite nicely.  The SVGAlib version of Maelstrom
is much smoother than the X11 version.


Sound:

	The next component was sound.  Dave Taylor, author of the Linux port
of DOOM!, worked with the author of the VoxWare sound driver for Linux,
enhancing the real-time sound capabilities of the sound driver.  Recently,
Terry Evans (tevans@cs.utah.edu) wrote a sound effects server for Linux that
can mix multiple sounds.  I adapted the algorithm for sound mixing for my
own sound server for Maelstrom.  Each loop of the mixer combines multiple
channels of the sound mixer into a single chunk of sampled data which is
sent to the audio device.  The original idea was to have a continuous loop,
first checking for input, and then writing a chunk of sound (or silence) to
the sound device.  This resulted in slow response time, because the a sound
event had to wait the entire cycle of combining the sound channels until it
could be acted upon.  I modified the original concept to support
asynchronous input.  Now, the loop has been simplified to a simple
continuous play of the sound channels, but at any time during the
compilation of a sound chunk, new sound data can be placed into channels, or
removed from the mixing channels.  This allows nearly instantaneous mixing
of sound effects, in response to sound events.

	I decided to run the sound server as a separate process from
Maelstrom. The sound server has to continuously play sampled data (sound or
silence) and respond instantly to sound events.  Maelstrom has to
continuously update animated graphics.  I thought the best way to perform
both of these functions simultaneously was to do them in separately running
processes, communicating through a private UNIX domain socket.  The socket
is set up for asynchronous I/O, and when one process sends a message to the
other, they are interrupted by a SIGIO signal.  A handler is set up for each
process, handling requests.  The sound server handles sound requests, and
sends a "sound done playing" message when it finishes playing a sound.

	This scheme works remarkably well, however there are still some
problems with it.  If the sound fragment size is too large, the write to the
sound device returns too soon, and the time the sound is played doesn't sync
with the time the program thinks the sound is played.  On slow systems,
sound requests can interrupt the write() system call, and if the requests
come too quickly, a single write will never complete.  The current
implementation tests the write() return value for EINTR, signaling it was
interrupted, and performs a goto to restart the write.  It also has to
re-set the signal handler at each interrupt, and can be interrupted during
request processing.  This can result in infinite recursion on the signal
handler on slow systems (this has not been observed on my system).

	Regarding the accuracy of Maelstrom sound, there is a trade-off
between the accuracy of the synchronization between Maelstrom and the sound
server, and the smoothness of sound play.  The fragment size needs to be
large enough so that the audio device continues playing while the Maelstrom
process runs, and small enough so that the sound write returns soon after a
sound is finished playing, i.e. not too much extra sound being played in a
chunk after then end of a sound clip.  I've found that 1024 bytes is a fair
number for the fragment size, weighing timing accuracy and smoothness of play.
The only time the timing accuracy is really noticeable is at the end of the
level in the bonus count-down screen.


The Game:

	The main body of the game, all of the hit detection, sprite
updating, movement, etc was originally written as approximately five
thousand lines of 68K assembler.  I rewrote and translated it all to
fourteen hundred lines of in-lined C++.  I did not change any of the logic of
the game, intending this port to be as faithful a representation of the
original as possible.  The structure of the game was preserved as much as
possible, changed only where the differences between the Macintosh and Linux
environment required them.  These changes were primarily in the graphics
interface.  Andrew Welch included the header file to his sound server, and
I emulated his entry point functions (the API) in my C++ sound-client class.

	The Maelstrom dialog boxes were captured from the screen of a
Macintosh using the System 7.5 screen capture facility (Command-Shift-3) and
then analyzed at the pixel level using 'xv', and recreated with custom
written Mac-like Dialog classes and a Font handling class that can translate
text strings, with Macintosh 'NFNT' resources, directly into blittable
bitmaps.  They work almost 100% exactly like the originals. 

	I thought about evolving some of the simple data structures used by
Maelstrom, (e.g. arrays, sprite structs) into some of the more advanced data
types, such as objects, supported by the C++ language.  This would improve
the _look_ of the code quite a bit, but would slow it down as well.  If I
were to do major redesigning of the way the game works, it might be
worthwhile, but since it works well, and works fast, I would not want to add
unnecessary data complexity.  One concession I made to the C++
object-oriented paradigm was implementing the graphics display driver
as a "FrameBuf" framebuffer graphics class.  I plugged in graphics modules
for both X11 and SVGAlib so that the same executable program can run in
both X Windows and Linux console environments.

	At this point, the Linux version of Maelstrom is Freeware, by
permission of Andrew Welch, the author of the original Macintosh version.
It includes source code for the Linux version, and boldly displays the
Ambrosia Software logo at startup.


Problems:

	When Maelstrom dies unexpectedly, it leaves shreds of shared memory
lying around the system.  These need to be removed by hand, or reclaimed by
Maelstrom itself.


Future Enhancements:

	I would like to find a way to reclaim shared memory that 
has been orphaned, possibly by using a Maelstrom-specific shared memory 
identifier.  I will look at the source to "ipcs" to find out how to 
search out shared memory on a system.

	I would like to port Maelstrom to the SGI.  A port to the SGI 
would be fairly simple, except for one thing.  I know nothing about 
the SGI sound interface.  All of the SGI systems I have access to do 
not have the sound interface documentation installed.

	I would like to eventually write an enhanced version of Maelstrom,
with additional bonuses, "rubber" asteroids, etc.  Maelstrom+?  This would
be in collaboration with Andrew Welch, and would require that I first...

	It would be nice to extend my Macintosh Resource class to be able
to write the Macintosh resource forks as well as read them.  I also want
to expand my sound class to be able to understand other sound formats
besides Macintosh sampled sound bites.


Conclusion:

	Ya HOO!!!  It can be done!!  My friends have played it and
pronounced it a very good job.  As far as I can tell, the Linux version of
Maelstrom is an authentic Maelstrom version, complete in every detail.  The
timing is accurate, response time is accurate, animation is smooth, sound is
clear and timely.

	I feel that I have demonstrated that games of good quality,
and real-time audio-visual applications of all kinds can be well written
and well used in the Linux/X11 environment.

Excuse me, I have to go play. :-)







APPENDIX A:			CREDITS


Thanks to all the people who helped this project be fulfilled.....

(a partial list follows)

Emily, who reminds me to take care of myself, without whom, I would 
eat only chips and salsa, stay up until 5 A.M. every night, and sleep 
through all my classes. :)

Ron Olsson, who's giving me credit for having fun. :)

Andrew Welch, the author of Maelstrom

Larry, for being picky, and praising perfection.

Dave, for letting me use your Mac all the time.

Whoever that C++ teacher was... ;-)

Paul Hargrove, author of the HFS file-system for Linux
(hargrove@sccm.stanford.edu)

Justin Kibell for inspiring me to write a good game for Linux.
(jck@citri.edu.au)

Terry Evans, author of that great mixer, "sfxserver"

The combined authors of "Inside Macintosh" -- invaluable!

The author of ResEdit

The author of GraphicsConverter for the Mac

John Bradley, author of "xv"

Dave Taylor, author of the "DOOM!" for Linux port, who first turned 
me on to MITSHM 

Guido van Rossum, a man named Guido who wrote "mac2bdf".

The XFree86 Team, for a great X11 windowing system! :)

Cliff at ARDI -- I still say mine is faster! ;-)

Manuel, who first showed me Maelstrom -- I'm addicted! :)


Of course, Linus and Linux-heads everywhere, I wouldn't have Linux 
without you....
