Doom Classic Code Review

iPhone developer Fabian Sanglard, before diving into the iPhone version of Doom, spent some time studying the original 1993-vintage Doom we all know and/or love in order to learn its many secrets. He's now posted a detailed article about the original software Doom renderer full of references to linedefs and segs and ssectors and all those things I understand just as vaguely now as I did back in the day. Most interestingly, the page includes some Quicktime videos showing the process of drawing a single Doom frame, slowed down considerably of course.

Share this post


Link to post

It's way over my level of understanding but still cool to read about. It's always funny to see the inner workings of stuff like 3d rendering, where the common person doesn't realize how much calculation and time-saving is going on.

Share this post


Link to post

Pretty cool. This might serve as a reference for some things in the Doom Wiki.

Share this post


Link to post

I was gonna say the same...damn..I'm interested in that article too!

Share this post


Link to post

Really interesting stuff. I've always wondered what it'd look like if you did a step-by-step of the rendering process.

Share this post


Link to post

Link works now - props to Fabien Sanglard for his amazing work...

Share this post


Link to post

This is probably the best Doom engine technical article I've ever read.

He did a great job explaining all the important functions and data structures. I was actually able to understand most of it.

I'd really like to know how he did the slow motion rendering. I'd love to try the same thing with Eternity. It would be nice to see how portals are rendered in my test map. I'm especially curious about how the engine draws textures and sprites in the infinite hallway.

Share this post


Link to post
Xtroose said:

This is probably the best Doom engine technical article I've ever read.

He did a great job explaining all the important functions and data structures. I was actually able to understand most of it.

I'd really like to know how he did the slow motion rendering. I'd love to try the same thing with Eternity. It would be nice to see how portals are rendered in my test map. I'm especially curious about how the engine draws textures and sprites in the infinite hallway.

(This is to the best of my understanding - SoM is the real expert, so he may have to correct me on some fine points)

Basically portal windows are accumulated during the drawing of the main scene and are then drawn afterward (not strictly recursively, as you might assume). Any portals accrued during THAT process are then subsequently drawn, etc. So just picture that exact entire process happening over again within smaller portions of the screen, and that's what you would see.

Eternity will not currently render the same portal during the same frame more than 8 times, so the level of recursive descent is inherently limited. This is necessary to prevent runaway rendering into empty portal windows :)

Share this post


Link to post

So that's how it works. I was sure there had to be a limit in the number of consecutive renders. Looks like a lot is going on behind the scenes, so I find it just amazing how fast the renderer is. SoM really did a great job. Thank you for taking the time to explain this to me.

Share this post


Link to post

That is a really nice article. There are couple of very minor errors (e.g., the worst case draw rate for a given pixel can be much higher than three if you consider psprites and the various HUD displays) but nothing earth shattering.

Share this post


Link to post
DaniJ said:

That is a really nice article. There are couple of very minor errors (e.g., the worst case draw rate for a given pixel can be much higher than three if you consider psprites and the various HUD displays) but nothing earth shattering.


I think the point of that was more to illustrate overdraw than to say that any pixels can only be written to 3 times in a frame. Although, it is worded awkwardly...

Share this post


Link to post

I'm glad some people enjoyed my article, I write this stuff mainly as memo for myself, but it is obviously a huge bonus when people like it.

The way it's done is actually not rocket science, here is the process if you want to do it on Eternity:

The approach is to limit the number of pixel drawn each frames, save the frame as a bitmap and generate a movie out of it.

I used two variables: max_num_pixels=0 and remaining_pixels_for_frame.

At the beginning on a frame in D_Display:

remaining_pixels_for_frame = max_num_pixels;
max_num_pixels++;
All draw operations are nicely grouped in r_draw.c, depending on the rendition quality (high or low), the engine will use either:
R_DrawColumn ()
R_DrawFuzzColumn ()
R_DrawTranslatedColumn ()
R_DrawSpan ()
or
R_DrawColumnLow ()
R_DrawFuzzColumnLow ()
R_DrawTranslatedColumnLow ()
R_DrawSpanLow ()
For each method, test:
if (remaining_pixels_for_frame >0 )
{
remaining_pixels_for_frame--
draw
}
Two issues:

- Doom never cleared the framebuffer because the entire screen was updated each frame, you need to do this otherwise you still have the menu or whatever was there before you started:

In D_Display:
memset(screens[0], 0, SCREENWIDTH * SCREENHEIGHT);
Each frame, the screen is saved as BMP:

In I_FinishUpdate:
memset(screenName,0,256);
sprintf(screenName,"./screens/draw%d.bmp",numFrames);
SDL_SaveBMP(screen,screenName);
I then used Quicktime to open an image sequence and generate an mp4.

For the iPhone version, it's a little bit more complicated because it's working with GL_TRIANGLES and GL_TRIANGLE_STRIP but the approach is the same: limit the number of triangles, read the framebuffer via a glReadPixels, write the frame.

In the end you get something like this:

http://fd.fabiensanglard.net/doomIphone/doomiPhonePreview.mov

Walls and Flats are drawn not in distance order but in textureID order to limit the number of openGL state change (something that you probably saw in Quake engine as well).
The video also illustrate the way the tessellation worked via the blue floor (you can see the triangles drawn as a fan, left to right)
If I find enough interesting stuff about iPhone version, I'll publish something.

EDIT: I don't believe Eternity is SDL based but you can as well generated a TGA, here is a code snippet from my dEngine:
int num_screenshot=0;

void dEngine_WriteScreenshot(char* directory)
{
	char* data;
	int i;//,j;
	FILE* pScreenshot;
	char fullPath[256];
	char num[5];
	char tga_header[18];
	char* pixel;
	char tmpChannel;
	int renderHeight = 480;
	int renderWidth = 320 ;
	
	sprintf(num, "%04d", num_screenshot++);
	//itoa(,num,3);
	
	memset(fullPath, 256, sizeof(char));
	strcat(fullPath,directory);
	strcat(fullPath,num);
	strcat(fullPath,".tga");
	
	data = calloc(renderHeight*renderWidth, 4);
	
	glReadPixels(0,0,renderWidth,renderHeight,GL_RGBA, GL_UNSIGNED_BYTE,data);
	
	pScreenshot = fopen(fullPath, "wb");
	
	memset(tga_header, 0, 18);
	tga_header[2] = 2;
	tga_header[12] = (renderWidth & 0x00FF);
	tga_header[13] = (renderWidth  & 0xFF00) / 256;
	tga_header[14] = (renderHeight  & 0x00FF) ;
	tga_header[15] =(renderHeight & 0xFF00) / 256;
	tga_header[16] = 32 ;
	
	
	
	fwrite(&tga_header, 18, sizeof(char), pScreenshot);
	
	//RGBA > BGRA
	pixel = data;
	for(i=0 ; i < renderWidth * renderHeight ; i++)
	{
		tmpChannel = pixel[0];
		pixel[0] = pixel[2];
		pixel[2] = tmpChannel;
		
		pixel += 4;
	}
	
	
	fwrite(data, renderWidth * renderHeight, 4 * sizeof(char), pScreenshot);
	
	fclose(pScreenshot);
	free(data);
}
It's no great code but it gets the job done.

Share this post


Link to post

Cool!

Eternity has code to write TGA files already, you'd just need to modify it slightly from the screenshot engine :) Also if you enable the old BOOM option for showing HOM, the screen will already be cleared at the start of each frame (though it might be necessary to change the HOM floodfill color from that obnoxious red to something like black).

Share this post


Link to post
fabinou said:

Lot's of awesome stuff


This is an awesome article. I've always wanted to understand more in depth how the Doom rendering worked. I've glanced at the code but the old procedural code hurts my brain. I'll have to try this stuff out when I have some free time. Thanks!

Share this post


Link to post

Great Article, answers a lot of questions I've had for over 10 years.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now