There is an interesting way to study the architecture of computers of the past. Find a program you know and try to figure out how it was ported.
A good choice for this would be DOOM. The 1994 megahit from id Software was ported to everything possible. The game is designed around a core clearly divided into layers. It is usually easy to find and read the implementation of the six I / o subsystems.
Another choice would be another world 1991 by Eric Shaya, in North America better known as Out Of This World. I would say that it is actually more interesting to study than DOOM, because of the polygonal graphics suitable for wild optimizations. In some cases, clever tricks allowed the game to run on hardware created five years before the game was released.
This series of articles is a journey through video game hardware from the early 90’s. From the Amiga 500, Atari ST, IBM PC, Super Nintendo, to the Sega Genesis. For each machine, I tried to find out how Another World is implemented.
At best, I was able to contact the original developer. In the worst cases, I had to deal with disassembled code myself. It was a fun adventure.
Another World 101
There’s quite a bit of code in Another World. The original version for the Amiga was reported to have only 6000 lines. The executable file for DOS under PC is only 20 KB. Amazing for such a huge game that came on a single 1.44 MiB floppy. This is because most of the business logic is implemented using bytecode. The another World executable is actually a virtual machine host that reads and executes uint8_t opcodes.
A virtual machine in Another World defines 256 variables, 64 threads, 29 opcodes, and three framebuffers (translated from PatientZero). That’s all. If you create a host for the VM that can handle this, you can start the game. If you can make a virtual machine fast enough to run at 20 frames per second, you can actually play the game.
The virtual machine’s graphics system uses a 320×200 coordinate system with a 16-color palette. The limit on the palette may be surprising, given that the Amiga 500 supports up to 32 colors. This was done purposefully, which allowed the graphics to be combined with another major platform of the time — Atari ST, which only supports 16 colors.
But there is a silver lining. This restriction has led to a unique style, which, after years, still pleases the eye.
Even when it was possible to use a certain palette for the scene, Eric Shay decided not to do it. During a collision with a “Monster” only three colors are used for it: black for the body, red for the eyes and beige for the teeth. Imagination did the rest.
A similar system for the palette was fully revealed in the opening scene. A very cheap change of palette made it easy to depict a lightning strike.
|The engine is also able to create translucency effects if there are only eight colors on the scene.|
|Here the colors are stored within [0x0,0x8].|
The beams from the Ferrari headlights are translucent. They are drawn with a special color 0x10, which does not exist because only 16 colors are available. The special value is interpreted as “read the frame buffer index, add 0x8, and return”. The last part of the trick is to cleverly select the next 8 colors in the palette.
|Transparency was not used so often in the game,
but it can be seen again during the experiment,
when lightning is about to teleport Lester to Another World.
Of the three framebuffers, two are used for double buffering, while the last one is used for saving the background (BKGD) composition. This optimization avoids redrawing all polygons of static backgrounds in favor of a simple copy operation.
In the next video, see how the new scene is drawn first in the BKGD buffer. Each new BKGD frame is completely copied to the double buffer. There are moving elements, such as Leicester, are drawn. Note that after the car is “parked”, it is also drawn in the BKGD buffer to minimize the number of polygons that will be drawn in subsequent frames.
Also note how the composition uses a simple algorithm of the artist, which, despite its simplicity, leads to unnecessary redrawing. This is not a problem, since it is largely amortized by a copy of BKGD.
Virtual machine opcodes
The following table shows 29 opcodes. Here you can find opcodes for thread management (THRD), framebuffer management (FB), and all register management operations. Most operations are “simple” to implement, with the exception of “COPY FB”,” FULL”, and ” LOW_POLY*”, which are complex in terms of performance.
Both DRAW_POLY_ * opcodes cover more than one cell. DRAW_POLY_BACKGROUND takes up half of the opcode space – from 0x80 to 0xFF. Very wasteful, but it’s a trick to save space. Using all operations starting with bit ” 1 ” allows 7 other bits to be transferred to the opcode space as rendering parameters. Since this opcode is used for background and cinematics, saving space is very important.
The SPRITE version uses all opcodes starting with bits “01”, while the remaining 6 bits are designed to encode [x, y] coordinates and zoom to draw the” sprites ” of Lester, friend, and enemies.
As mentioned earlier, 26 of the 29 opcodes are easy to implement. The real problem when porting this game was manipulating pixels within the limits of the bus and CPU bandwidth. This series will look at how framebuffers were manipulated during the port and how DRAW, FILL, and COPY opcodes were handled.