Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
  • Sign in to follow this  

    Doom Classic Code Review


    Linguica

    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.

    Sign in to follow this  


    User Feedback

    Recommended Comments

    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 comment


    Link to comment

    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 comment


    Link to comment

    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 comment


    Link to comment
    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 comment


    Link to comment

    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 comment


    Link to comment

    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 comment


    Link to comment
    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 comment


    Link to comment

    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 comment


    Link to comment

    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 comment


    Link to comment
    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 comment


    Link to comment


    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

×