Playstation Doom Polygons

image

Sony PlayStation



The story of the PlayStation began in 1988 when Nintendo and Sony began working together on an optional CD-ROM reader for the SNES console. Under the terms of the agreement, Sony was able to independently develop games for this platform and retained control of the Super Disc format - two unusual concessions by Nintendo.

The project developed before the CES '91 exhibition, at which Sony announced a collaboration on Play Station. The next day, at the same exhibition, Nintendo, to the surprise of Sony, announced that it had instead entered into a partnership agreement with Philips (on much more favorable terms). The loyal and publicly humiliated Sony tried to appeal to the Sega board of directors, which immediately rejected the idea. In an interview with 2013, SEGA CEO Tom Kalinske recalled the decision of the council.

“This is a stupid idea, Sony does not know how to develop equipment. They do not know how to write software. Why do we mess with them? ” - SEGA Board of Directors

And they were not mistaken, Sony really had little experience in working with games. She almost did not show interest in them, with the exception of the initiative of one person - Ken Kutaragi. From the moment he saw his daughter play at the Nintendo Famicom, Ken has been convincing Sony that he needs to enter this market. Despite the recommendations of Sony vice presidents, he even developed for Nintendo the audio chip (SPC700) used in SNES.

Most Sony executives considered this a risky bet, but Kutaragi found support in the person of Sony CEO Norio Oga. In June 1992, Ken was allowed to start creating a gaming system from scratch. To calm the board of directors, the “father of the PlayStation,” as they later called him, was transferred to the financially independent parent company Sony Music, and he began work on what would eventually become the “PlayStation” (already without a space in the name).

Initially, developers could not decide whether the architecture should focus on sprite 2D graphics or polygon 3D graphics. However, the success of the Virtua Fighter game released in October 1993 by Sega on Japanese arcade machines destroyed all doubts: the PSX will follow the 3D path.

The culmination of the project came two years later, when Sony Computer Entertainment was created on December 3, 1994 and the console was released in Japan. It gained instant success and was sold on the first day with a circulation of 100 thousand devices, 2 million devices after six months, and 102 million devices throughout their life.


Sony PSX (PS1, PlayStation). Photo: Wikipedia

Architecture



The heart of the machine is the MIPS 32-bit RISC R3000A processor with a frequency of 33.8688 MHz [3] in combination with 2 MB DRAM. It is noteworthy that this chip also has a Motion Decoder (MDEC), which provides video playback with a resolution of 320x240 at a frequency of 30 fps. Next to MDEC, we see the Geometry Transform Engine (GTE) coprocessor, used to perform fast fixed-point mathematical operations on vectors and matrices. The system as a whole is unable to work with floating point numbers.

The GPU is a “black box” controlled by the central processor using “commands”. It has 1 MB VRAM, unavailable to the CPU. In many ways, his work resembles the work of the wonderful Immediate mode OpenGL: he draws textured polygons that can be “shaded” using vertex colors. The graphics pipeline is fixed and cannot be programmed. There is no Z buffer (depth buffer).

The sound processing unit Sound Processing Unit (SPU), like the GPU, is a black box. It can process 24 channels, has 512 KB of SRAM for storing audio (in ADPCM format) and is capable of mixing CD-ROM audio tracks with its audio channels. The abbreviation SRAM does not mean “Static RAM”, but “Sound RAM”.

The most daring decision of Sony engineers was the choice of data carrier. They chose not cartridges, but a dual-speed CD-ROM module, which reduced the cost of games and significantly increased the available volume (up to about 650 MB). The disadvantages are the low transfer rate (300 KB / s) and the monstrously large head installation time (300 ms).

There is no blitter in the console. The programming model of the machine was that the developers did not touch the "bare" iron. However, there is a DMA controller for transmitting data from CD / DRAM to VRAM / SRAM.

Video system


Despite the fact that the video system supports 24-bit color, very few games have used it (with the exception of Heart Of Darkness backgrounds [4] ). In practice, it can be said with good conscience that most games used 16-bit colors with a 1-bit mask.



PSX color space with a depth of 15 bits per pixel

A stunning feature of the video system is the organization of VRAM, which is completely dependent on the developer. 1 MB VRAM is considered an array of 1024 x 512 x 16 bits that could be freely used. The application of double or triple buffering is performed in a trivial way, because the area is simply enough to reserve, draw and transfer to the output system. Textures are also in VRAM, next to the frame buffers. To write to the frame buffer, GPU commands for rendering textured triangles / quadrangles are launched.

Textures can have various formats. There are two direct sources of 16-bit and 24-bit colors, as well as sources based on a palette called the Color Look Up Table (CLUT). 8-bit and 4-bit CLUTs are supported.

In terms of resolution, the console was limited to NTSC and PAL television standards. For the US and Japan markets, the developer could choose a horizontal resolution of 256, 320, 384, 512, or 640 pixels. The vertical resolution was either 240 pixels (when skipping every second raster line), or 480 pixels when alternating. Both vertical modes operated at a frequency of 60 Hz. The only difference between NTSC and PAL was an increased vertical resolution (256/512 instead of 240/480) and a lower refresh rate (50 Hz).

NTSC mode (60Hz) PAL (50Hz) Note
=================================================== ========

0 256 x 240 256 x 256 Non-interlaced
1 320 x 240 320 x 256 Non-interlaced
2 512 x 240 512 x 256 Non-interlaced
3 640 x 240 640 x 256 Non-interlaced
4 256 x 480 256 x 512 Interleaved
5 320 x 480 320 x 512 Interleaved
6 512 x 480 512 x 512 Interleaved
7 640 x 480 640 x 512 Interleaved
8 384 x 240 384 x 256 Non-interlaced
9 384 x 480 384 x 512 Interleaved


Pay attention to the modes with a horizontal resolution of 384 pixels (8 and 9), which, judging by their id, were added later.

DOOM on PSX


DOOM was ported to PSX by Williams Entertainment with support from id Software. It took a team of five people a little less than a year [5] [6] to port the engine, change resources and modify the game so that everything worked “only” on 3.5 MB of RAM.

“The graphics have been degraded: textures are reduced in size, sprites, monsters and weapons too. [...] Sometimes we cut animation frames. " - Harry Tisley

The finished result was released on November 16, 1995. It is considered the best console port of the game, and some aspects even surpass the PC version due to color tops and CD-quality music.

“So far, this is the best DOOM!” - John Romero

Study plan



Due to the nature of development, the DOOM structure is based on a kernel that uses six subsystems for I / O. Most of the time I studied the three parts that I found most interesting, namely the CD-ROM-based file system, SPU-based audio, and GPU-based video, because they are unique to the PSX architecture.

The DOOM source code for the PSX was never published, but it turned out to be no problem at all. Available information is enough.

The first source is the awesome PSY-Q SDK, which was the “official” tool used by PSX developers of the time. There is a lot of documentation on it, presented as a set of PDF files. Such an abundance of information only confirms all the good that I heard about the PSX's friendliness to developers. The library (i.e., libcd, libds) developed by Psygnosis is also well documented. It was very nice to see clear explanations, especially compared to the almost complete lack of information about other consoles (yes, I'm talking about SNES).

Another source of information is the many external tools available today. ISOBuster allowed me to open the contents of the CD. PSound helped scan LCD files. The stunning ability of the no $ PSX emulator to trace GPU and SPU commands has become real gold.

But perhaps I was most impressed with the huge love of DOOM for PSX from the fan community. A complete reverse engineering of the game was performed. PSXDOOM-RE stands out especially because it is a C codebase that can be compiled using the PSY-Q SDK into a full-fledged PSX game. The code is highly reliable because it was obtained by rewriting each function of the machine code in C.

The amazing world of CDs


Before starting to study the implementation of the file system, I took a short excursion to better understand the wonderful world of CDs.

For 20 years, from 1980 to 2000, several volumes of specifications have been released that reveal this topic. Together, they are called "Rainbow Books." The first book in the series, “Red Book,” contains specifications for an audio compact disk (CD). The “Yellow Book” is a complement to the “Red Book”, it adds data specifications for CD-ROM and ISO-9600. The Orange Book has added specifications for CD-DA, CD-R (Recordable) and CR-WR (Rewritable). The White Book is dedicated to Video-CD (VCD). The Green Book talks about CD-Interactive (CD-I). The Blue Book presents Enhanced-CD (ECD) data for multimedia support. The Scarlet Book is dedicated to Super Audio (SACD), which adds high definition audio. The Purple Book lists the Double Density CD (DDCD) specification, which has increased disk capacity from 650 MB to 1.3 GB. Finally,Cyan Book details 9660 file system specifications[7] .


Rainbow Books contains everything we know about the CD.

As an absolute minimum, we need to understand that PSX CDs usually consist of sectors, each of which contains 2048 bytes of data. Sectors are grouped into tracks (which may be data or sound). The tracks are grouped in session. Information of data tracks can be arranged using the standard ISO-9660 file system, however, the game can also have hardcoded sector addresses.

Inside the DOOM game CD



If you look inside the CD-ROM using ISOBuster, you can see that DOOM consists of one session containing eight tracks [8] . Seven of them are CD-quality sound tracks played between missions and in the final scene. The final track (# 7) even uses digitized demon voices. Track number 6 is especially noteworthy because it seems to have been taken straight from the rave party. It turns out that this is music played on the super-secret card 59 “Club DOOM” (a secret card accessible only from a secret card) [9] . Let me let you appreciate her insanity. Before starting, check the sound volume.


The remaining track (number 1) is ISO-9660, which contains the game engine and most resources. After exploring the many DOOM ports, I naively expected an engine called DOOM.EXE, a PSXDOOM.WAD resource file, and possibly a manifest. I was very mistaken. 287 files [10] [11] were found on the track , including 60 .WAD, 120 .IMG and countless .LCD.

Data is ordered by level (five files per card).

File Name Description
=================================================== =====

MAPDIR0 / MAP01.WAD Standard Geometry (BSP / Reject / ...)
MAPDIR0 / MAPTEX01.IMG Textures of planes / walls
MAPDIR0 / MAPSPR01.IMG All sprites created by THINGS
MUSIC / MUSLEV1.LCD Playable Music
SNDMAPS1 / MAP01.LCD All sounds made by THINGS

When asked why everything was arranged in a new way, Crash Bandicoot developer Andy Gavin partially answered in an interview with Ars Technica. [ translation on Habré]

“It takes about 1/3 of a second to move the head to any point on the CD.”

Due to the fact that the head positioning speed was close to 300 ms (which is confirmed by the PSY-Q documentation [12] ), the developers at Williams Entertainment could not save the clear architecture of the DOOM engine, which stored all the resources in one file (for example, DOOM.WAD) and downloaded them on request. On the PSX, this would lead to a terrible frame rate.

The developers solved the problem by throwing a seemingly endless amount of bytes on the CD. All the resources necessary for the level were stored in five files containing the geometry of the map, texture. sprites, sound effects and music. This is very expensive, but it eliminated the need to access the CD during the game process.

Interesting fact:in the list of files on the data track there is a file called PSXDOOM.WAD (4 806 088 bytes), but it is used only for loading palettes and several menu images. Probably more actively used in the development process.

Take the first map as an example: the total amount of downloaded data has been reduced from 4 MB to 900 KB.

File Name Size (in bytes)  
=====================================  	
MAPDIR0 / MAP01.WAD 28 196
MAPDIR0 / MAPTEX01.IMG 90 744
MAPDIR0 / MAPSPR01.IMG 590 344
MUSIC / MUSLEV1.LCD 61 232
SNDMAPS1 / MAP01.LCD 143 632
=====================================
Total: 914,148

Knowing that resources occupy 914 KB, you might think that there was a lot of unused DRAM left. But you need to remember that it also had to fit an executable file of 428 KB, as well as a stack that changes at run time. In fact, only about 1 MB of DRAM was available at runtime.

An interesting fact: while examining the source code of PSXDOOM-RE, I found the P_LoadBlocks function, which tries to read from CD up to four times [13] , after which it surrenders. This is one of the pleasures of working with easily scratched media.

I did not expect the installation time of the CD-ROM head to affect the game architecture so much. Some games, like Crash Bandicoot, were created from scratch with a page system for streaming data from a CD at runtime. In the case of DOOM, the engine was not capable of this. The CD is not used during the game, with the exception of one particular song (yes, from the Club DOOM level).

The id Software guys have never been a fan of the trade-off between capacity and speed that CD-ROMs offered.

« - . , CD. - , Crash 'n Burn — ? . , CD , 3DO . - CD- DOOM, „ DOOM“. , . .

, CD. ». — (ATARI EXPLORER ONLINE, 22 1994 )

An interesting fact: events inside the DOOM game were triggered using "stretch marks." When crossing a line with a special property, a special function was called. In the "legacy" version of the engine there was no property that allowed you to play songs. To play Club DOOM, a special linedef action was created (number 142) [14] . It's amazing how much extra effort it took to create this level. Probably the developers really liked to have fun on the raves.

The Case of the Missing Archvile



Conducting research for my Game Engine Black Book, I couldn’t fully figure out this phrase by designer Harry Teesley:

“Archvile had twice as many frames of animation than any other monster, and we could not cram it into the PSX. Neither his attack nor the resurrection effect could be lost. He was too big. " - Harry Tisley (designer) in an interview with doomworld.com

It was obvious that the problem was not the 650 megabyte capacity of the CD, but I did not understand what was the limiting factor - DRAM or VRAM.

Having figured out the limitations of the CD-ROM, I realized that the problem was not in storing the sprite on a CD or even transferring it from DRAM to VRAM. The problem is to fit everything into DRAM.

Interesting fact: ArchVile is completely removed from the source code of PSXDOOM-RE. Even his #DEFINE is commented out [15] .

#define CC_ZOMBIE  "Zombieman"
#define CC_SHOTGUN  "Shotgun Guy"
#define CC_HEAVY  "Heavy Weapon Dude"
...
#define CC_HELL   "Hell Knight"
//#define CC_ARCH "Arch-Vile"
#define CC_SPIDER "The Spider Mastermind"
#define CC_CYBER  "The Cyberdemon"
#define CC_HERO   "Our Hero"

SPU Programming


The SPU chip only understands one format, namely ADPCM. It is capable of mixing up to 24 channels (including the CD audio track) and has powerful audio manipulation functions.

To tame this beast, the DOOM PSX uses the libWESS library (Williams Entertainment Sound System), written by sound engineer Scott Patterson. The library is quite powerful, it is able to recreate the MiDI system, in which a heavy bank of notes (called sound font) is controlled by a musical score that takes up little space. It can also manipulate in real-time sound attributes such as volume, tone, note speed, and ADSR functions (Attack, Decay, Sustain, and Release). All music played during gameplay is generated using libWESS. With one exception, you guessed it, this is Club DOOM, which is played from CD track number 6.

WESS uses two proprietary file formats. These are .WMD files containing music scores and sound effects, and .LCD files, which are PSX VAG format (no title) and contain ADPCM samples. When DOOM starts, the libWESS library loads all sound effects (SFX, 89 pieces) and musical scores (19 pieces) stored in a small (55 KB) file called DOOMSND.WMD. She also loads into the SRAM “always used” samples published by the character, doors, etc.

MUSIC / DOOMSFX.LCD -> SRAM
MUSIC / DOOMSND.WMD -> DRAM

After loading the map, libWESS opens MUSIC / MUSLEV% .LCD, which contains the ADPCM samples used in the music of this card, and SNDMAPS% / MAP %%. LCD, which contains the ADPCM samples needed for enemies at this level. All ADPCM samples are loaded directly into SRAM and do not take up space in DRAM.

DOOM on PSX: GPU


In the area of ​​video generation, Williams Entertainment needed to solve two problems: a small amount of VRAM (we will come back to this later) and the lack of correct texture mapping taking into account the perspective.

“Aaron Siler and I worked on versions for the Nintendo 64 (this game was quite different) and for the Playstation. These were the first versions that were not written on bare metal, because both Sony and Nintendo forced developers (at least third-party ones) to write APIs and did not give them documentation on hardware registers. At first, the SGI culture particularly limited developers, but gradually Nintendo loosened its grip.

A funny story about developing a version for the Playstation: Aaron and I started with a different engine architecture, which rendered the world triangles because the console provided them with full hardware acceleration. In the case of N64, this worked perfectly, because it had perspective-correct rendering with subpixel accuracy (the result of the influence of SGI), but on the Playstation affine texture mapping was performed with integer coordinates, so the large triangles of the walls and floor looked AWESOME. ” - John Carmack (Game Engine Black Book: DOOM)

To understand how “terrible” it looked, below I showed three walls rendered with affine and prospectively correct texturing.


Perspective Texturing


Affine (PSX)

Note that there is no problem with a straight wall, and as the angle of perspective increases, the distortion becomes more noticeable.

The same perception problem faced Rage Software developers when porting DOOM to SEGA Saturn.

« , id Software. , WAD Saturn, , 3D-. , , 3D- . , PC. , , : SH2 , PC, 68000 .

, » — ( DOOM Saturn) RetroGamer №134.

Perhaps, since the developers on PSX had more time, they were able to solve the problem of prospectively correct texture mapping by turning the GPU polygon renderer into line renderer.

"I said: backup everything (then there were no version control systems yet!), We will make the game completely different." We used equipment that rendered triangles that represented columns or rows of one pixel wide, just like in the assembler code on a PC, and it worked great. It turned out that the more common solution on the Playstation was tessellation of geometry along two axes, but I really liked that Doom seemed less "twitchy" than most games for the Playstation of that time. "- John Carmack (Game Engine Black Book: DOOM)

Thanks to the PSXDOOM-RE project [16], we can find out how it was implemented. The rendering pipeline has been completely rewritten and divided into two stages. This will be discussed in more detail below, but we can actually see that the R_Render_Wall rendering function transmits hard-coded drawing commands with a width of 1 pixel.

void R_Render_Wall(...) {
  .
  int x1 = ... ; // Left end of wall.
  int x2 = ... ; // Right end of wall.
  .
  while(x1 < x2) {
    .
    setRGB0(wallpoly);

    setXY3(wallpoly,
      x1    , ypos1 - 1,
      x1 + 1, ypos2 + 1,
      x1    , ypos2 + 1);		

    setUV3(wallpoly,
         upos, v0,
         upos, v1,
         upos, v1);  
    .
    x1 += 1;
  }
}

Walls are rendered in one pixel wide columns. Check out the API, which resembles Immediate mode in OpenGL.

Interesting fact: Sony's hardware designer retained the MIPS processor’s i-cache, but disabled its d-cache to turn it into a 1K high-speed scratchpad. Wall / plane rendering procedures used this scratchpad extensively.

VRAM GPU Management


To learn how VRAM control is performed, I chose a curious way: I used the function of viewing real-time VRAM emulator no $ PSX. This function displays the entire array of pixels 1024 x 512 x 16 bits (albeit in a distorted form). The view function also shows the entire list of GPU instructions transmitted by the central processor in each frame.


No $ PSX - God sent us an emulator that allows you to look inside the GPU.

A careful study of VRAM first frame allows you to learn a lot.


The first frame of the game displayed with No $ PSX.

The most obvious are two areas of 256 x 240 x 16 bit in the upper left corner, which are the frame buffers (therefore, the game uses double buffering). It is worth noting that 256x240 is the lowest resolution possible on a PSX.

Under the frame buffers is a colorful set of pixels - the CLUT palette. Note the shades of red, they mean that when the screen turns red when the player takes damage, pre-calculated palettes are used.

In the upper right corner we see textures that are strangely compressed horizontally and have “wrong” colors. It happened because textures use 8-bit indices of the CLUT described above.

Let's talk about fire in DOOM on PS1 again


In 2018, I studied how the effect of fire was realized [17] [ translation on Habré]. It was great to come back to him again when exploring the GPU commands. Notice that no $ PSX marks the target area of ​​each command in red.

Stage 1: the flame is updated in RAM and loaded into VRAM (CpuToVram command).


Stage 2: the flame texture is drawn four times along the screen (QuadTex command). The flame texture has a width of 32 texels, but the GPU is used to draw it with a width of 64 pixels. Bilinear filtering is not possible here, the nearest texels are sampled.


Stage 3: the Final DOOM plate (QuadTex command) is drawn on top of everything.


Full frame, team by team


A study of the commands transmitted in the frame showed that the engine is completely different from the PC version. In it, the world was circled from a short distance into the distance. First, all the walls are rendered. On the second pass, a vertical gap is filled between them (visplane) (including the sky). In the third (last) passage, sprites are rendered, starting with the farthest. All this was done with zero redrawing pixels.

On PSX, rendering is a little more rude. Everything is rendered in one pass, performed starting from afar. The visplane system that the PC used to fill the void between the walls is not here. Thanks to a new concept called “leaves,” the plane and walls are rendered in the order of the subsectors. Such operations in “true 3D” became possible due to the active use of the GPE matrix projection functions. Sprites are also rendered simultaneously with walls / planes without overlap and truncation checks, which results in a small amount of redrawing.

void R_RenderPlayerView(void) {
  R_BSP();         // Phase 1
  R_RenderSKY();   // Phase 2
  for (all subsectors from phase 1) {
    R_RenderAll(subsector)
  }
  R_Render_Hud_Weapons();
}

Let's look at each step in detail, starting with the sky, which is first rendered using CopyRectRaw. no $ PSX shows VRAM at the time the frame is completed, but allows you to go back through the history of commands. Sky pixels are visible only because no $ PSX has problems perceiving this hack with columns of one pixel width (other emulators, such as ePSXe, can cope no better [18] ), but all these pixels will be rewritten. Notice that under the textures of the sky all the markers of the keys to the doors are collected in an atlas.


Then the BSP is traversed in order from far to near. For each subsector, all walls / planes / sprites are rendered. If you are familiar with DOOM BSP, then probably remember that the doombsp compiler only kept solid segments of subsectors. To ensure the rendering of planes, a new concept of “leaves” was added to the PSX version, in which BPS separation segments (invisible) were stored. These segments are projected into the screen space to generate plane boundaries. This is a much more “clean” approach, because it allows you to get rid of hacks with screen space and bugs, for example, from the well-known “trail of slug”.


In the next VRAM shot, planes from the same subsector as the wall we saw above are rendered as triangles 1 pixel high. Also pay attention to the texture of the walls / planes, they all have the same size. This feature simplifies the selection of VRAM textures and avoids fragmentation.


We are still in the same subsector. Now sprites are rendered as quadrangles (Quad). These sprites include enemies, shells, partially transparent walls.


For fun, we’ll show plasma shells.


We are approaching the end of the rendering commands. Here, by mixing the rectangle, the weapon is rendered.


Last Step: Interface Rendering (HUD).


VRAM overflow!



Working with the GPU was an exercise in allocating space in VRAM. In the case of frame buffers, CLUT, static content (interface), and even walls / planes, the task is elementary, because they all have the same size. But with sprites, things are a little more complicated.

Due to the fact that sprites have different sizes, they lead to fragmentation. Moreover, textures can cover large areas of the screen and repeat, and sprites are often unique and an unpredictably large number of them may be required in each frame. In the worst case, a frame requires a certain number of sprites that cannot be placed in VRAM. This is called a Texture Cache Overflow, and this problem cannot be solved. When this happens, the game crashes and displays a cryptic error message [19]reminding some of us of the ominous “No more visplanes”.


The more sprites you see at the same time, the more VRAM is used.

The difference between PAL and NTSC


In conclusion of the video chapter, I decided to see how NTSC is converted to PAL. Unfortunately, like in many other games, the DOOM PSX did not take into account the increase in vertical resolution. When playing DOOM on PS1 in Europe, you saw a vertically compressed image with noticeable black borders.

After I learned about the principles of VRAM, it’s hard for me to blame the developers. If you look closely at the VRAM scheme in NTSC, you will notice that increasing the vertical resolution of the frame buffer violates the entire data allocation structure. It would be impossible to store textures under it. Or you would have to move CLUT to another location. Too much cost with relatively little benefit, and even despite the fact that the black borders interfered with the game, I agree that it was not worth it.

Acknowledgments


Thanks to Eric Vazquez (author of PSXDOOM-RE), Samuel Villarreal (one of the developers of PSXDOOM-RE) and Dan Leslie (professional developer of games for PlayStation 1) for generously sharing their knowledge with me.


All Articles