home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 August / Chip_2003-08_cd1.bin / oddech / maelstrom / maelstrom.exe / Maelstrom / Docs / Porting.Paper < prev    next >
Text File  |  1999-12-07  |  20KB  |  391 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.             ON PORTING MAELSTROM TO LINUX
  15.  
  16.  
  17.             by
  18.               Sam Lantinga
  19.  
  20.  
  21.  
  22.  
  23.  
  24. Graphics:
  25.  
  26.     X11 was originally designed as a well defined client-server
  27. protocol, device independent, network capable windowing system.  It was
  28. fully capable of supporting the applications of the time: menus, text
  29. editors, simple bitmap graphics, etc.  However, many applications today
  30. require a faster, more responsive graphics interface.  Any real-time video
  31. application requires much faster video access and updates than the X11
  32. system was originally designed to provide.  Examples of these types of
  33. applications include teleconferencing systems, MPEG video players, real-time
  34. modeling object modeling, etc.  The pioneers in real-time audio/visual
  35. techniques have been (and probably will be) fast-action video games.  Arcade
  36. games stress hardware more than almost any other application, requiring
  37. real-time video, real-time audio, and good response time to user input.
  38. More advanced games run networked as well, requiring fast, well designed
  39. transmission control protocol and routing of network packets (not discussed
  40. in this paper.)
  41.  
  42.     These real-time services are easy to provide in a simple,
  43. single-user system such as the DOS PC, making it one of the most popular
  44. platforms for video games.  It is slightly more complex to provide these in
  45. a well designed single-user windowing system such as the Macintosh. 
  46. However, even there, it is possible to give full access to the hardware to
  47. any applications that require it.  This performance is much harder to
  48. provide in a multi-user time-sharing environment such as UNIX X11.
  49.  
  50.  
  51.     The objective of this project was to show that fast-action games,
  52. and hence, other high-speed interactive applications, can be written for the
  53. X11 environment.  The target application for this project was the Macintosh
  54. shareware game "Maelstrom".  From the press release:
  55.  
  56.     "Maelstrom 1.4 is a marriage of the venerable Asteroids concept 
  57.      with new digitized sound effects, 3D graphics and high resolution
  58.          256 color animation.  'With fantastic art and graphics by Ian 
  59.      Gilman and Mark Lewis, Maelstrom does for the tired old Asteroids-
  60.      style games what the Mazda Miata did for sporty roadsters...'"
  61.  
  62. As such, Maelstrom seemed the perfect target for the X11 environment.
  63. The operating system chosen for the port was Linux version 1.2.9, a free
  64. UNIX-like operating system for the PC.  The development hardware was a
  65. DX2/66 VLB PC with a Cirrus Logic 5426 video board and a Sound-Blaster Pro
  66. 8-bit sound-card.  The X11 environment used was XFree86 version 3.1, using
  67. the SVGA X server with the built in MITSHM extension.
  68.  
  69.     After obtaining the source code through a non-disclosure agreement
  70. with the author, Andrew Welch, I began the port.  The most immediate
  71. problems I ran into had nothing to do with graphics speed, or hardware
  72. access.  How was I going to be able to get the icons, sprites and sounds out
  73. of the Macintosh resource fork files?  Throughout the port, whenever I would
  74. run into these problems, there would be some resource, some paper or program
  75. written that would help me solve the problem.  A (nearly) complete list of
  76. these and credits are in an appendix to this paper.  For the specs on the
  77. Macintosh resource files, I went to the well done series "Inside Macintosh",
  78. published by Addison and Wesley.  With the specs in hand, I coded C++
  79. classes that could parse Macintosh resource forks (e.g. dialog box
  80. specifications, custom fonts, sprites, icons, sound clips, etc.) directly
  81. from the UNIX file-system.  How was I able to get the resource forks there
  82. in the first place?  That was made possible by the new HFS file-system
  83. driver for Linux, written by Paul Hargrove.  Once I had routines that could
  84. extract sprites, color icons, and color-maps, I went to work on the graphics
  85. interface.
  86.  
  87.     I had a game for X11, called "Xboing" written by an Australian
  88. Justin Kibell, which used the Xpm library for full color animation in a
  89. well designed blockout-style game.  (For more information, his WWW page is
  90. http://144.110.160.105:9000/~jck/xboing/xboing.html)  However, using the
  91. Xpm library, copying pixmap sprites from application to X server, his
  92. animation has a great deal of "flicker" where the animation seems to flash
  93. as it goes along.  His method of dealing with sounds (designed for the 
  94. sun /dev/audio interface) is not tightly synchronized, leading to sounds
  95. and animation for a single event occurring several seconds apart.  The effect
  96. is similar to that of a high flying jet -- the sight of the jet precedes
  97. the sound of the jet by several seconds.
  98.  
  99.     Another game, recently ported to the Linux X11 environment, is
  100. the popular "DOOM!" game, by ID Software.  DOOM! uses frame-buffer based
  101. animation, where multiple frames are blitted to the screen per second,
  102. giving the appearance of smoothly flowing graphics.  The X11 port takes
  103. advantage of an extension to the X11 protocol known as "MITSHM", or 
  104. "MIT Shared Memory Extension" in which the client application and the X
  105. server share a segment of memory that corresponds to an off-screen "image".
  106. Graphics are drawn to this off-screen image and a single call to the X11
  107. server tells it to copy the image to a window on the physical screen.
  108. This is much faster than sending each frame through the client-server
  109. communications connection.  For a 320x200 resolution window, this results
  110. in reasonable performance.  The game can be played smoothly and in this
  111. game, sound is tightly coupled to the actions in the game, for realistic
  112. play.
  113.  
  114.     In all but the fastest systems, the MITSHM off-screen image
  115. technique is limited to 320x200 resolution.  Any larger, and the X server
  116. cannot copy the image to the window fast enough for smoothly flowing
  117. graphic frames.  Most X systems run in higher resolution, turning the
  118. full-screen virtual reality game "DOOM!" into a tiny window on the desktop. 
  119. Maelstrom was designed for 640x480 resolution, and uses moving sprite
  120. animation, so the MITSHM off-screen image technique is not suitable for the
  121. Maelstrom port.  The Xpm method of erasing and drawing sprite images would
  122. be perfect for Maelstrom, if the images could be copied fast enough to
  123. provide smoothly moving animation.  With sometimes upwards of 30 moving
  124. objects on the screen at once, the copying would have to be fast indeed.
  125.  
  126.     I combined both approaches.  Using a technique I haven't heard of
  127. before, I used the feature that a pixmap can be used as a window background,
  128. in combination with the fact that a shared memory segment can be associated
  129. with a pixmap, to create a shared memory pixmap that was the background of
  130. a window!  I can then directly manipulate the pixels in the background and
  131. simply call XClearArea() to refresh the changed area of the screen.  This
  132. technique gives an incredible speedup over the shared memory image method,
  133. because the window is not modified by copying an entire frame, but by merely
  134. refreshing the existing one.  All graphics drawing is done on the background
  135. of the window and then placed on the display by calling XClearArea().  This
  136. technique completely breaks down the client-server network relationship of
  137. the X11 environment, but for certain high-speed graphics applications, the
  138. speed increase is definitely worthwhile.
  139.  
  140.     Even with the speedy technique of shared background pixmaps, there
  141. needs to be some way of copying the original sprite pixel data into the
  142. shared background (hereafter referred to as the frame-buffer.) A technique
  143. suggested by Andrew Welch, was to compile the sprites into streams of pixels
  144. and opcodes.  Instead of running a loop, copying each pixel independently
  145. after checking to see if it needs to be put on the screen, compiled sprites
  146. are streamed onto the frame-buffer using the largest copy possible, switching
  147. on the next opcode to see how big a skip to make, streaming a next copy,
  148. skipping, etc.  Copying compiled sprites was 50 percent faster than using
  149. the old sprite/mask technique. An additional advantage was that compiled
  150. sprites took an average of 50 percent less storage space than the old
  151. sprites and masks.  Compiled sprites ran into problems however, when
  152. clipping them on the edge of the screen.  sprite pixel-maps and masks have
  153. the advantage that each row of pixels is a fixed width, and all you have to
  154. do is skip a certain amount into each row to clip an edge of the sprite. 
  155. Each row of a compiled sprite is variable width, and the checking required
  156. to clip them would take longer than the pixel/mask method.  I wrote a
  157. routine to dynamically recompile a compiled sprite, based on clipping
  158. conditions, but it too was slightly slower than the pixel/mask method.  For
  159. sprites with absolute coordinate positions, I use the pixel/mask image
  160. updating during clipping and for small sprites with relative coordinate
  161. positions (thrust sprites, etc) I use compiled sprite recompiling during
  162. clipping.  This combination of techniques resulted in a good graphics speed
  163. for high-speed play of the game Maelstrom.
  164.  
  165.     Color mapping is a big issue in the X11 environment.  Since the
  166. screen is shared with other applications, and there are limited color
  167. entries in the color table, you need some way of getting the colors you
  168. need, without depriving other applications of the colors they need.  There
  169. are several ways to approach this.  "Netscape", a world wide web browser,
  170. just grabs all the free color cells and allocates the colors it wants. 
  171. "xv", an image viewer, allocates an orthogonal spectrum of color cells and
  172. maps the colors it wants to this spectrum of colors.  "DOOM!" allocates a
  173. private color-map, fills it with it's own colors and uses that.  This has the
  174. side-effect of turning all other windows on the X display into psychedelic
  175. color friezes.  Since Maelstrom uses a full 256 color table, it needs more
  176. colors than are generally free in a shared color-map.  The approach I took
  177. was similar to that of xv -- I allocated a spectrum of colors, and then 
  178. mapped the colors I wanted to this spectrum, plus the colors already in 
  179. the colormap.  A command-line option can be used to tell Maelstrom to
  180. allocate a private color-map for "true" colors, the same way "DOOM!" does.
  181.  
  182.     Color mapping is only an issue in 8-bit displays.  Truecolor
  183. displays can display thousands or millions of colors simultaneously and
  184. require different color techniques.  My hardware will only support 8-bit
  185. color, and so Maelstrom doesn't currently support Truecolor displays.
  186.  
  187.     The VGA port posed an interesting problem, compared to X11
  188. graphics.  In the X11 paradigm, graphic updates are queued until explicitly
  189. flushed, or until the next call for input.  Under SVGAlib, as soon as a
  190. graphics request is made, the screen is updated.  This results in "flicker",
  191. as all of the sprites are erased, then moved, and then they are updated.
  192. My solution was to create an expandable stack of refresh updates, and then
  193. only update the screen when explicitly told to, or when asked for input.
  194. This gets rid of the flicker quite nicely.  The SVGAlib version of Maelstrom
  195. is much smoother than the X11 version.
  196.  
  197.  
  198. Sound:
  199.  
  200.     The next component was sound.  Dave Taylor, author of the Linux port
  201. of DOOM!, worked with the author of the VoxWare sound driver for Linux,
  202. enhancing the real-time sound capabilities of the sound driver.  Recently,
  203. Terry Evans (tevans@cs.utah.edu) wrote a sound effects server for Linux that
  204. can mix multiple sounds.  I adapted the algorithm for sound mixing for my
  205. own sound server for Maelstrom.  Each loop of the mixer combines multiple
  206. channels of the sound mixer into a single chunk of sampled data which is
  207. sent to the audio device.  The original idea was to have a continuous loop,
  208. first checking for input, and then writing a chunk of sound (or silence) to
  209. the sound device.  This resulted in slow response time, because the a sound
  210. event had to wait the entire cycle of combining the sound channels until it
  211. could be acted upon.  I modified the original concept to support
  212. asynchronous input.  Now, the loop has been simplified to a simple
  213. continuous play of the sound channels, but at any time during the
  214. compilation of a sound chunk, new sound data can be placed into channels, or
  215. removed from the mixing channels.  This allows nearly instantaneous mixing
  216. of sound effects, in response to sound events.
  217.  
  218.     I decided to run the sound server as a separate process from
  219. Maelstrom. The sound server has to continuously play sampled data (sound or
  220. silence) and respond instantly to sound events.  Maelstrom has to
  221. continuously update animated graphics.  I thought the best way to perform
  222. both of these functions simultaneously was to do them in separately running
  223. processes, communicating through a private UNIX domain socket.  The socket
  224. is set up for asynchronous I/O, and when one process sends a message to the
  225. other, they are interrupted by a SIGIO signal.  A handler is set up for each
  226. process, handling requests.  The sound server handles sound requests, and
  227. sends a "sound done playing" message when it finishes playing a sound.
  228.  
  229.     This scheme works remarkably well, however there are still some
  230. problems with it.  If the sound fragment size is too large, the write to the
  231. sound device returns too soon, and the time the sound is played doesn't sync
  232. with the time the program thinks the sound is played.  On slow systems,
  233. sound requests can interrupt the write() system call, and if the requests
  234. come too quickly, a single write will never complete.  The current
  235. implementation tests the write() return value for EINTR, signaling it was
  236. interrupted, and performs a goto to restart the write.  It also has to
  237. re-set the signal handler at each interrupt, and can be interrupted during
  238. request processing.  This can result in infinite recursion on the signal
  239. handler on slow systems (this has not been observed on my system).
  240.  
  241.     Regarding the accuracy of Maelstrom sound, there is a trade-off
  242. between the accuracy of the synchronization between Maelstrom and the sound
  243. server, and the smoothness of sound play.  The fragment size needs to be
  244. large enough so that the audio device continues playing while the Maelstrom
  245. process runs, and small enough so that the sound write returns soon after a
  246. sound is finished playing, i.e. not too much extra sound being played in a
  247. chunk after then end of a sound clip.  I've found that 1024 bytes is a fair
  248. number for the fragment size, weighing timing accuracy and smoothness of play.
  249. The only time the timing accuracy is really noticeable is at the end of the
  250. level in the bonus count-down screen.
  251.  
  252.  
  253. The Game:
  254.  
  255.     The main body of the game, all of the hit detection, sprite
  256. updating, movement, etc was originally written as approximately five
  257. thousand lines of 68K assembler.  I rewrote and translated it all to
  258. fourteen hundred lines of in-lined C++.  I did not change any of the logic of
  259. the game, intending this port to be as faithful a representation of the
  260. original as possible.  The structure of the game was preserved as much as
  261. possible, changed only where the differences between the Macintosh and Linux
  262. environment required them.  These changes were primarily in the graphics
  263. interface.  Andrew Welch included the header file to his sound server, and
  264. I emulated his entry point functions (the API) in my C++ sound-client class.
  265.  
  266.     The Maelstrom dialog boxes were captured from the screen of a
  267. Macintosh using the System 7.5 screen capture facility (Command-Shift-3) and
  268. then analyzed at the pixel level using 'xv', and recreated with custom
  269. written Mac-like Dialog classes and a Font handling class that can translate
  270. text strings, with Macintosh 'NFNT' resources, directly into blittable
  271. bitmaps.  They work almost 100% exactly like the originals. 
  272.  
  273.     I thought about evolving some of the simple data structures used by
  274. Maelstrom, (e.g. arrays, sprite structs) into some of the more advanced data
  275. types, such as objects, supported by the C++ language.  This would improve
  276. the _look_ of the code quite a bit, but would slow it down as well.  If I
  277. were to do major redesigning of the way the game works, it might be
  278. worthwhile, but since it works well, and works fast, I would not want to add
  279. unnecessary data complexity.  One concession I made to the C++
  280. object-oriented paradigm was implementing the graphics display driver
  281. as a "FrameBuf" framebuffer graphics class.  I plugged in graphics modules
  282. for both X11 and SVGAlib so that the same executable program can run in
  283. both X Windows and Linux console environments.
  284.  
  285.     At this point, the Linux version of Maelstrom is Freeware, by
  286. permission of Andrew Welch, the author of the original Macintosh version.
  287. It includes source code for the Linux version, and boldly displays the
  288. Ambrosia Software logo at startup.
  289.  
  290.  
  291. Problems:
  292.  
  293.     When Maelstrom dies unexpectedly, it leaves shreds of shared memory
  294. lying around the system.  These need to be removed by hand, or reclaimed by
  295. Maelstrom itself.
  296.  
  297.  
  298. Future Enhancements:
  299.  
  300.     I would like to find a way to reclaim shared memory that 
  301. has been orphaned, possibly by using a Maelstrom-specific shared memory 
  302. identifier.  I will look at the source to "ipcs" to find out how to 
  303. search out shared memory on a system.
  304.  
  305.     I would like to port Maelstrom to the SGI.  A port to the SGI 
  306. would be fairly simple, except for one thing.  I know nothing about 
  307. the SGI sound interface.  All of the SGI systems I have access to do 
  308. not have the sound interface documentation installed.
  309.  
  310.     I would like to eventually write an enhanced version of Maelstrom,
  311. with additional bonuses, "rubber" asteroids, etc.  Maelstrom+?  This would
  312. be in collaboration with Andrew Welch, and would require that I first...
  313.  
  314.     It would be nice to extend my Macintosh Resource class to be able
  315. to write the Macintosh resource forks as well as read them.  I also want
  316. to expand my sound class to be able to understand other sound formats
  317. besides Macintosh sampled sound bites.
  318.  
  319.  
  320. Conclusion:
  321.  
  322.     Ya HOO!!!  It can be done!!  My friends have played it and
  323. pronounced it a very good job.  As far as I can tell, the Linux version of
  324. Maelstrom is an authentic Maelstrom version, complete in every detail.  The
  325. timing is accurate, response time is accurate, animation is smooth, sound is
  326. clear and timely.
  327.  
  328.     I feel that I have demonstrated that games of good quality,
  329. and real-time audio-visual applications of all kinds can be well written
  330. and well used in the Linux/X11 environment.
  331.  
  332. Excuse me, I have to go play. :-)
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340. APPENDIX A:            CREDITS
  341.  
  342.  
  343. Thanks to all the people who helped this project be fulfilled.....
  344.  
  345. (a partial list follows)
  346.  
  347. Emily, who reminds me to take care of myself, without whom, I would 
  348. eat only chips and salsa, stay up until 5 A.M. every night, and sleep 
  349. through all my classes. :)
  350.  
  351. Ron Olsson, who's giving me credit for having fun. :)
  352.  
  353. Andrew Welch, the author of Maelstrom
  354.  
  355. Larry, for being picky, and praising perfection.
  356.  
  357. Dave, for letting me use your Mac all the time.
  358.  
  359. Whoever that C++ teacher was... ;-)
  360.  
  361. Paul Hargrove, author of the HFS file-system for Linux
  362. (hargrove@sccm.stanford.edu)
  363.  
  364. Justin Kibell for inspiring me to write a good game for Linux.
  365. (jck@citri.edu.au)
  366.  
  367. Terry Evans, author of that great mixer, "sfxserver"
  368.  
  369. The combined authors of "Inside Macintosh" -- invaluable!
  370.  
  371. The author of ResEdit
  372.  
  373. The author of GraphicsConverter for the Mac
  374.  
  375. John Bradley, author of "xv"
  376.  
  377. Dave Taylor, author of the "DOOM!" for Linux port, who first turned 
  378. me on to MITSHM 
  379.  
  380. Guido van Rossum, a man named Guido who wrote "mac2bdf".
  381.  
  382. The XFree86 Team, for a great X11 windowing system! :)
  383.  
  384. Cliff at ARDI -- I still say mine is faster! ;-)
  385.  
  386. Manuel, who first showed me Maelstrom -- I'm addicted! :)
  387.  
  388.  
  389. Of course, Linus and Linux-heads everywhere, I wouldn't have Linux 
  390. without you....
  391.