June 30, 2012

Quake 3 Source Code Review: Architecture (Part 1 of 5) >>

Since I had one week before my next contract I decided to finish my "cycle of id". After Doom, Doom Iphone, Quake1, Quake2, Wolfenstein iPhone and Doom3 I decided to read the last codebase I did not review yet:

idTech3 the 3D engine that powers Quake III and Quake Live.

The engine is mostly an evolution of idTech2 but there are some interesting novelties. The key points can be summarized as follow:

I was particularly impressed by :

As usual I wrote numerous notes that I have cleaned up and synthesized into drawings. I hope it will save time to some people but also encourage others to read more code and become better engineers.

Edit : Thanks for the support :) !

First contact

Since the venerable ftp.idsoftware.com was recently decommissioned the code can be found on id Software's GitHub account:


 
    git clone https://github.com/id-Software/Quake-III-Arena.git    



When it comes to comprehend a huge codebase I prefer to use XCode: SpotLight speed, Command-click to find definition and strings highlight make the tool more powerful than Visual Studio. But opening Quake III project showed that code rotting is not always about the code but also about the tools: XCode 4.0 is unable to open the Quake III XCode 2.0 projects.

In the end I used Visual Studio 2010 Professional on Windows 8: Upon installation of Visual Studio 2010 Productivity Power Tools the toolset was actually enjoyable.

The first striking thing is that the Visual Studio workspace is not made of one project but eight. Not all of them are used depending if the build is DEBUG or RELEASE (especially game,cgame and q3_ui : the virtual machines projects). Some of the projects are never used (splines and ui).

A table is better to summarize what project is contributing to which module:

Projects Type DEBUG Builds RELEASE Builds Comments
botlib Static Library botlib.lib botlib.lib A.I
cgame Dynamic Libary/Bytecode cgamex86.dll -
game Dynamic Libary/Bytecode qagamex86.dll -
q3_ui Dynamic Libary/Bytecode uix86.dll -
quake3 Executable quake3.exe quake3.exe
renderer Static Library renderer.lib renderer.lib OpenGL based
Splines Static Library Splines.lib Splines.lib Used NOWHERE !
ui Dynamic Libary/Bytecode uix86_new.dll - Used for Quake III Arena.

Trivia : idTech3 working title was "Trinity". Since idTech4 was called "Neo" I assumed it was from the "Matrix" franchise...but id Software stated in interview with firingsquad.com that it was named after "Trinity River in Dallas":



    John : I've got a couple of engine things that I'm working on, as far as research.

    FS : So is one of those things Trinity? I think there's been a little confusion about "Trinity."

    John : I was never really certain how this got as confusing as it did to everybody. After Quake, when 
    I was starting on new rendering technologies and everything, everybody was just calling it "the next engine" 
    or whatever. Michael Abrash suggested we just take Intel's tack of naming your next project after a river near 
    you. We have the Trinity River in Dallas, and so it was just like "Trinity Engine," the next step.

    

Edit ( July 07, 2012 ) : Jeremiah Sypult contacted me after publication of this article: He "unrotted" the Mac OS X build with an XCode 4.0 project for Quake3. I have fixed it further and you can get it on here on github. You will need the Quake III Arena baseq3 (not the demo version) and be sure to use the parameters "+set vm_game 0 +set vm_cgame 0 +set vm_ui 0" in order to use the dylib virtual machines. Open /code/quake3.xcworkspace and it builds in one click !!

Architecture

A convenient way to understand an architecture is to first look at the software as a black box receiving input (upper left arrows) and generating output (bottom arrows):



Then look how the inputs flows towards the outputs in a whitebox fashion with the 6 modules (quake3.exe, renderer.lib, bot.lib, game, cgame and q3_ui) interacting as follow:



Two important things to understand the design:

  1. Every single input (keyboard, win32 message, mouse, UDP socket) is converted into an event_t and placed in a centralized event queue (sysEvent_t eventQue[256]). This allows among other things to record (journalize) each inputs in order to recreate bugs. This design decision was discussed at length in John Carmack's .plan on Oct 14, 1998.

  2. Explicit split of Client and Server (this was outlined in a Q&A I did with John Carmack):
    
    
        Fabien Sanglard: What do you think summarize the main innovations in idTech3 besides:
                           - Bot I.A 
                           - Virtual Machine: Combining QuakeC portability and Quake2 dll speed.
                           - SMP & Shaders renderer.
                           - New Network code.
     
        John Carmack: The explicit split of networking into a client presentation side and the server logical side was really the right 
        thing to do. We backed away from that in Doom 3 and through most of Rage, but we are migrating back towards it.  All of the Tech3
        licensees were forced to do somewhat more work to achieve single player effects with the split architecture, but it turns out that
        the enforced discipline really did have a worthwhile payoff.
    
    
    

    • The server side is responsible for maintaining the state of the game, determine what is needed by clients and propagate it over the network. It is statically linked against bot.lib which is a separate project because of its chaotic development history mentioned in page 275 of "Masters of Doom":

      	   
      	   
          To make matters worse, a fundamental ingredient of the game - the bots - was missing. Bots 
          were characters controlled by the computer. A good bot would blend in with the action and flesh 
          out the scene like a robotic extra, as well as interact with the player. For Quake III, a deathmatch
          only game, bots were essential for single-player action. They were implicitly complex because they 
          had to behave like human beings.
          
          Carmack had decided, for the First time, to delegate the job of creating these bots to another 
          programmer in the company. But he failed to follow up. Once again, Carmack incorrectly assumed 
          that everyone was as self-motivated and adept as he was. He was wrong.
          
          When Graeme struggled to rein in the work, it was discovered that the bots were completely ineffective. 
          They din't behave at all like human beings. They behaved, basically, like bots. The staff began to panic.
          By March 1999, they had reason to be scared.
          [..]
          
          In the end the bot were farmed out to a well-known mod maker in the Netherlands, who heroically brought 
          them to life. (Note from Fab: This is Mr.Elusive: Jan Paul van Waveren).
      	   
      	   
      	   

    • The client side is responsible for predicting where entities are (latency compensation) and render the view. It is statically linked against renderer project: A separate project that would have allowed a Direct3D or even software renderer to be plugged in very easily.

The code

From a code point of view here is a partially unrolled loop that illustrate the event production and consumption by the client and server:



    int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
       
        Com_Init
        
        NET_Init
        
        while( 1 )
        {
            // Common code
            IN_Frame()  // Add Win32 joystick and mouse inputs  as event_t to unified event queue.
            {
	             IN_JoyMove                                    
	             IN_ActivateMouse
	             IN_MouseMove
            }
	        
            Com_Frame
            {
                 // Common code
                 Com_EventLoop    // Pump win32 message, UDP socket and console commands to the queue (sysEvent_t eventQue[256]) 
                 Cbuf_Execute
                 
                 // Server code
                 SV_Frame
                 {
                     SV_BotFrame                                 // Jump in bot.lib
                     VM_Call( gvm, GAME_RUN_FRAME, svs.time )    // Jump in Game Virtual Machine where game logic is performed
                     
                     SV_CheckTimeouts
                     SV_SendClientMessages                       // Send snapshot or delta snapshot to connected clients
                 } 
                 
                 // Common code
                 Com_EventLoop
                 Cbuf_Execute
                  
                 // Client code
                 CL_Frame
                 {
                     CL_SendCmd                                 // Pump the event queue and send commands to server.
                     
                     SCR_UpdateScreen
                        VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME);   // Send message to the Client Virtual Machine (do Predictions).
                             or
                        VM_Call( uivm, UI_DRAW_CONNECT_SCREEN); // If a menu is visible a message is sent to the UI Virtual Machine.
                     
                     S_Update                                   // Update sound buffers
                 }
            }
            
        }
    }

Here is a fully unrolled loop that I used a a map while digging into the source code.

An interesting thing to notice here that perfectly illustrates how paramount the virtual machines are: Nowhere we see a call to RE_RenderScene: the function that performs culling and issue OpenGL commands. Instead what happen is:

  1. Quake3.exe sends a message to the Client VM: CG_DRAW_ACTIVE_FRAME which signal that a refresh is needed.
  2. The Virtual Machine performs some entity culling and prediction then call for OpenGL rendition via a Quake3 system call (CG_R_RENDERSCENE).
  3. Quake3.exe receives the system call and actually calls RE_RenderScene.



    OpenGL                Quake3.exe                                 Client Virtual machine
   --------               ----------                                ----------------------
      |                        |                                                |
      |                        |                                                |
      |                   VM_Call(CG_DRAW_ACTIVE_FRAME)----------------->  CG_DrawActiveFrame
      |                        |                                           CG_DrawActive
      |                        |                                           trap_R_RenderScene
      |                        |                                           syscall( CG_R_RENDERSCENE, fd )
      |                   RE_RenderScene <--------------------------------------|
      |                        |                                                |
      | <----------------------|                                                |
      |                        |                                                |
      
              

Statistics

Here are some stats from cloc:


    -----------------------------------------------------------------------------------------------
                                                 files          blank        comment           code
    -----------------------------------------------------------------------------------------------
    cloc-1.56.exe code common                     559          48630          73501          233952
    cloc-1.56.exe lcc                             116           2270           1513           28067
    cloc-1.56.exe q3asm q3map                      44           4987           5565           22877
    cloc-1.56.exe q3radiant                       206          11870          13113           54922
    -----------------------------------------------------------------------------------------------
    TOTAL                                         919          68293          95509          341994
    -----------------------------------------------------------------------------------------------


On a pie chart we can vividly see how unusual the proportion are since 30% of the codebase is dedicated to tools:


This is explained partly because idtech3 features a ANSI C compiler: The open source Little C Compiler (LCC) is used to generate bytecode for the virtual machines.


Memory allocation

Two custom allocators at work here:


A Better tomorrow

With this code review it seems I ran out of great 3D engine source code to read. But many great things are on the way in the CG world. I am especially looking forward the Occulus Rift VR kits:





Building ,coding and understanding the testbed will be a lot of fun. It should arrive in late July so until then: Happy Hacking !

Recommended readings

Masters of Doom for history.
The two best compiler books to understand fully the Quake Virtual Machines.
A paper to understand LCC Intermediate Representation

Next part

The Rendition Model


Add a comment



Name Homepage
E-mail
(Will not appear online)
Comment



Comments (34)


#1 - 7 legged jumping sipder - 06/30/2012 - 17:37
Stop lazy loading the images. It makes the browser scroll like a fuck randomly when they load. Fuck this nonsense. Make your blog awesome and just load all the images at once. Or at least use img width="..." height="..." to have that space reserved so the fucking browser doesnt fucking jump. fuck.
#2 - Rich Jones - 06/30/2012 - 19:27
You've done such a fantastic job of deconstructing these games that I'm tempted to start a fundraiser for you to get one of these VR kits - you clearly know these code bases well enough to make something awesome out of all of them now!
#3 - You can't fix stupid. - 06/30/2012 - 21:01
Comment #1 is exactly the reason why high-quality blogs like this should disable comments. Idiot.
#4 - nuclear - 06/30/2012 - 21:29
comment#3: comment #1 makes perfect sense. This delayed image loading is LAME and ANNOYING.

Other than that, nice review.
#5 - Bill Forster - 06/30/2012 - 21:34
Thanks for sharing the insights gained in this fashion. I particularly enjoyed the "Architecture" section with your beautiful diagrams. Well done.
#6 - jeremiah sypult - 06/30/2012 - 21:34
I sincerely appreciate that you finally tackled a source code review of Quake3/id Tech 3! Your source code reviews have been a great resource for me while I've been learning to program.

If you're interested in taking a peek at Quake3 on Mac OS X, you might be interested in taking a look at my fork on github at http://github.com/jeremiah-sypult/Quake-III-Arena. I did my best to replicate the Visual Studio projects/solution with separate sub-projects and static libraries within an Xcode 4 workspace. The code rot was a problem like you mentioned and my efforts to update the project for a modern Intel build are not perfect since I had to disable QVM usage due to my inexperience dealing with that sort of code & assembler. If you had any interest in building Quake3 on Mac OS X, hopefully my efforts will be useful to you in some way.

Thanks for your great contributions and all the best,

.jeremiah sypult
#7 - Fabien Sanglard - 06/30/2012 - 21:40
@jeremiah

Thanks for "unrotting" the MacOS X build. I will definitely take a look and maybe try to fix the QVM interpreter so I can do some profiling with XCode Instruments.

Great job and thanks again :) !

Fabien
#8 - anonymous - 06/30/2012 - 21:45
You use "unrolled" when you mean "inlined" (or "expanded")--"unrolled" refers to loops being replicated.

You want "rendering", not "rendition", which means something completely different.
#9 - A Nonny Moose - 06/30/2012 - 23:29
May I ask what tool you used to create the q3_workspace_architecture2.png image?

Wonderful articles by the way, I'm enjoying them immensely.
#10 - sdfd - 07/01/2012 - 01:11
Comment #1 - Maybe it's time to upgrade your 486 SX processor and IE 1.0 browser.
#11 - Fabien Sanglard - 07/01/2012 - 01:53
@A Nonny Moose:

I use gliffy for my drawing ;) !

Take care,

Fabien
#12 - Roee - 07/01/2012 - 03:03
Really cool. Try out Source Insight for browsing large codebases - It's a clunky old editor, but I haven't seen anything get close to it in terms of usability for ridiculously large codebases
#13 - fatetract - 07/01/2012 - 04:37
I'm not seeing _any_ images. They just don't work without javascript.
#14 - Philip Swink - 07/01/2012 - 05:51
FWIW, I have javascript turned off, so looking at the images is a PITA that requires searching through the source and selecting text to copy to the address bar. I have no idea what it's supposed to gain. All modern browsers render the page around unloaded images, as long as width and height are specified.
#15 - BaconThatSteak - 07/01/2012 - 09:38
Excellent write up. I am also looking forward to reading your review of the Quake 1 code base!

Cheers!
#16 - A - 07/01/2012 - 12:12
Just to point out, commenter #1, *no* formatting should be in the markup according to w3c standards. It should be in the CSS. So not only are you an idiot, you don't know what you are on about either.
#17 - angel - 07/01/2012 - 16:22
please , keep doing this kind of articles , best i've read , i use to play quake3.

congrats
#18 - Nico - 07/02/2012 - 02:58
Great article once again!
Thanks a lot!!
#19 - msc - 07/03/2012 - 01:51
thanks for this.

thought you might like http://mortoray.com/2012/06/11/whats-to-love-about-c/ ;-)
#20 - kaka - 07/05/2012 - 11:39
Are you doing something odd with images on this site? They don't show up in Instapaper (that's how I read all long articles) and I get gray boxes in Safari.
#21 - Rufus - 07/05/2012 - 13:30
You truely are a hero, thanks very much!!!
#22 - Fabien Sanglard - 07/05/2012 - 13:53
@kaka: The image load via lazy loading (on the fly). This is done to save bandwidth.
#23 - rholdorf - 07/07/2012 - 19:34
Back then I was a Q3A addicted, used to play with the skeleton and could dodge point blank missile shots. Nowadays I'm a fat lazy developer. Anyway, I loved to play Q3A, and surelly will love to explore and play, this time with the code. My development experience is restricted to Windows. I was going to hack the code using Visual Studio when I saw there is a Mac Xcode project available as well, so I decided to give it a try. The problem is that I'm an Xcode noob. So far I managed compile it, but upon launching it blinks blue and says it "could not initialize OpenGL". I suspect it has something to do with the parameters "+set vm_game 0 +set vm_cgame 0 +set vm_ui 0" which I have no idea where to set. Any help?

Good articles, all of them! Thanks for sharing!
#24 - Timotei - 07/09/2012 - 13:21
Hello Fabien,

Congrats for this and the rest of awesome reviews. Definetely worth reading.

Just a small suggestion: Use the Visual Assist X 3rd party tool. It's better than the Productivity tools in a lot of ways, especially when working with big codebases :)

Regards,
Timo
#25 - Hypnose Erfahrungen in Frankfurt am Main - 07/16/2012 - 20:39
I am really impressed with your writing skills as
well as with the layout on your weblog. Is this a paid theme or did you modify it yourself?
Either way keep up the nice quality writing, it is rare
to see a great blog like this one nowadays.
#26 - alvare - 08/15/2012 - 23:38
You are definitely my favourite person so far this year, your website is a heaven of Game Programming and Design.

Also, I'm currently trying to do my first real game, which coincidentally is a shmup in Cave's earlier style, not very far from Treasure's style in Ikaruga, so your "Shmup" code is a bless for me right now (though I'm more of a C++ programmer).

Lovely article this one by the way, Quake is truthfully one of the greatest games ever :)
#27 - Tracie - 09/12/2012 - 16:44
I needed to thank you for this great read!! I absolutely enjoyed every bit of it.
I have got you saved as a favorite to look at new stuff
you postÂ…
#28 - Baris - 09/24/2012 - 07:08
Comment #1 is absolutely right. You shouldn't do everything just because you can. We're not dialing some number to get online anymore.

Other than that, this is perfect article.
#29 - Will Lam - 10/26/2012 - 15:32
Loved this game while playing it as a teenager.. its' a bit over my head, but I can still appreciate your post :)
#30 - Andrew - 10/30/2012 - 02:39
Heh, I personally don't mind lazy loading.

Also, if you're looking to save bandwidth, I very very highly recommend cloudflare.com
#31 - michael - 01/20/2013 - 05:39
Well done thanks..
#32 - Madhu - 02/19/2013 - 02:57
Thank you so much for sharing your knowledge.
Currently I am doing some research on networking methodologies and I though I can get some inspiration from Quake 3 code. I actually downloaded Mac version of your quake port and unfortunately when I run the game I am getting " 'macosx_glimp.h' file not found" error. Can you please help me fixing this?
#33 - Scrotos - 02/19/2013 - 16:47
You might want to point people towards ioquake3 at http://ioquake3.org as it's a bugfixed and modernized version of Quake 3. It retains the same gameplay elements but has been ported to MIPS, SPARC, ARM, and native x86-64. Compiles on Linux, FreeBSD, OS X, Windows, and I think SGI-Irix and Solaris. Several standalone games use it as a base, as well, and it's a much better codebase to look into for anyone wanting to tinker with it on modern systems.

Heck, people with Visual Studio and XCode knowledge would be welcome to contribute for those project files. Most of the maintainers use Linux and gcc with the makefile so those who prefer VS or XCode sometimes lag in support.
#34 - PLEASE! - 03/31/2013 - 11:17
PLEASE PLEASE PLEASE fix the lazy image loading, it's the most annoying web design choice I have EVER seen on the internet since 1995!

 

@2012