An ode to the father of the electric age.
View
Windows 7 : =========== git clone https://github.com/TTimo/doom3.gpl.git
MacOS X : ========= git clone https://github.com/badsector/Doom3-for-MacOSX-
base
folder containing the Doom 3 assets. Since I did not want to waste time extracting them from the Doom 3 CDs and updating them: I downloaded the Steam version. It seems id Software team did the same since the Visual Studio project released still contains "+set fs_basepath C:\Program Files (x86)\Steam\steamapps\common\doom 3"
in the debug settings!neo
subfolder.Projects | Builds | Observations | |
Windows | MacO SX | ||
Game | gamex86.dll | gamex86.so | Doom3 gameplay |
Game-d3xp | gamex86.dll | gamex86.so | Doom3 eXPension (Ressurection) gameplay |
MayaImport | MayaImport.dll | - | Part of the assets creation toolchain: Loaded at runtime in order to open Maya files and import monsters, camera path and maps. |
Doom3 | Doom3.exe | Doom3.app | Doom 3 Engine |
TypeInfo | TypeInfo.exe | - | In-house RTTI helper: Generates GameTypeInfo.h : A map of all the Doom3 class types with each member size. This allow memory debugging via TypeInfo class. |
CurlLib | CurlLib.lib | - | HTTP client used to download files (Staticaly linked against gamex86.dll and doom3.exe). |
idLib | idLib.lib | idLib.a | id Software library. Includes parser,lexer,dictionary ... (Staticaly linked against gamex86.dll and doom3.exe). |
idlib.a
and gamex86.dll
but the core of the engine was still closed source.libc
is extensively used.Filesystem
) are in the Doom3.exe project. This is a problem since gamex86.dll needs to load assets as well. Those subsystems are dynamically loaded by gamex86.dll from doom3.exe (this is what the arrow materializes in the drawing). If we use a PE explorer on the DLL we can see that gamex86.dll export one method: GetGameAPI
:LoadLibrary
.GetGameAPI
in the dll using win32's GetProcAddress
.GetGameAPI
.gameExport_t * GetGameAPI_t( gameImport_t *import );At the end of the "handshake", Doom3.exe has a pointer to a
idGame
object and Game.dll has a pointer to a gameImport_t
object containing additional references to all missing subsystems such as idFileSystem
.typedef struct { int version; // API version idSys * sys; // non-portable system services idCommon * common; // common idCmdSystem * cmdSystem // console command system idCVarSystem * cvarSystem; // console variable system idFileSystem * fileSystem; // file system idNetworkSystem * networkSystem; // network system idRenderSystem * renderSystem; // render system idSoundSystem * soundSystem; // sound system idRenderModelManager * renderModelManager; // render model manager idUserInterfaceManager * uiManager; // user interface manager idDeclManager * declManager; // declaration manager idAASFileManager * AASFileManager; // AAS file manager idCollisionModelManager * collisionModelManager; // collision model manager } gameImport_t;Doom 3's view on Game/Modd objects:
typedef struct { int version; // API version idGame * game; // interface to run the game idGameEdit * gameEdit; // interface for in-game editing } gameExport_t;Notes : A great resource to understand better each subsystems is the Doom3 SDK documentation page: It seems to have been written by someone with deep understanding of the code in 2004 (so probably a member of the development team).
cloc
:./cloc-1.56.pl neo 2180 text files. 2002 unique files. 626 files ignored. http://cloc.sourceforge.net v 1.56 T=19.0 s (77.9 files/s, 47576.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 517 87078 113107 366433 C/C++ Header 617 29833 27176 111105 C 171 11408 15566 53540 Bourne Shell 29 5399 6516 39966 make 43 1196 874 9121 m4 10 1079 232 9025 HTML 55 391 76 4142 Objective C++ 6 709 656 2606 Perl 10 523 411 2380 yacc 1 95 97 912 Python 10 108 182 895 Objective C 1 145 20 768 DOS Batch 5 0 0 61 Teamcenter def 4 3 0 51 Lisp 1 5 20 25 awk 1 2 1 17 ------------------------------------------------------------------------------- SUM: 1481 137974 164934 601047 -------------------------------------------------------------------------------
#Lines of code | Doom | idTech1 | idTech2 | idTech3 | idTech4 |
Engine | 39079 | 143855 | 135788 | 239398 | 601032 |
Tools | 341 | 11155 | 28140 | 128417 | - |
Total | 39420 | 155010 | 163928 | 367815 | 601032 |
lcc
codebase (the C compiler used to generate QVM bytecode) .idMath::InvSqrt
and spacial localization optimizations are here but most of the code just tries to use the tools when they are available (GPU Shaders, OpenGL VBO, SIMD, Altivec, SMP, L2 Optimizations (R_AddModelSurfaces
per model processing)...).const
placement).idCommonLocal commonLocal; // OS Specialized object idCommon * common = &commonLocal; // Interface pointer (since Init is OS dependent it is an abstract method int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { Sys_SetPhysicalWorkMemory( 192 << 20, 1024 << 20 ); //Min = 201,326,592 Max = 1,073,741,824 Sys_CreateConsole(); // Since the engine is multi-threaded mutexes are initialized here: One mutex per "critical" (concurrent execution) section of code. for (int i = 0; i < MAX_CRITICAL_SECTIONS; i++ ) { InitializeCriticalSection( &win32.criticalSections[i] ); } common->Init( 0, NULL, lpCmdLine ); // Assess how much VRAM is available (not done via OpenGL but OS call) Sys_StartAsyncThread(){ // The next look runs is a separate thread. while ( 1 ){ usleep( 16666 ); // Run at 60Hz common->Async(); // Do the job Sys_TriggerEvent( TRIGGER_EVENT_ONE ); // Unlock other thread waiting for inputs pthread_testcancel(); // Check if we have been cancelled by the main thread (on shutdown). } } Sys_ShowConsole while( 1 ){ Win_Frame(); // Show or hide the console common->Frame(){ session->Frame() // Game logic { for (int i = 0 ; i < gameTicsToRun ; i++ ) RunGameTic(){ game->RunFrame( &cmd ); // From this point execution jumps in the GameX86.dll address space. for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) ent->GetPhysics()->UpdateTime( time ); // let entities think } } session->UpdateScreen( false ); // normal, in-sequence screen update { renderSystem->BeginFrame idGame::Draw // Renderer front-end. Doesn't actually communicate with the GPU !! renderSystem->EndFrame R_IssueRenderCommands // Renderer back-end. Issue GPU optimized commands to the GPU. } } } }
Sys_StartAsyncThread
which indicate that Doom3 is multi-threaded. The goal of this thread is to handle the time-critical functions that the engine don't want limited to the frame rate:idCommonLocal commonLocal; // Implementation idCommon * common = &commonLocal; // Interface manipulated in the codeThis way the compiler can determine statically the method to call: No vtable lookup. It is actually a double win since the pointer does not have to be dereferenced at runtime either since its address is known at compile time.
IN_frame()
.dmap
is a complete departure from the traditional bsp builder. I reviewed it to the deep down on a dedicated page.Today morning, the current project I was working on went live successfully. Yep, it may not sound that big to anyone else. Buts its big for me. This is the first project I have lead from offshore. And one where I have lost considerable amount of sleep and peace of mind. Its been five years since I started my career, and this is one of those milestones I have been striving for since day one. And although I have been part of many smooth releases and go-lives in the past, I never thought that this release would be so taxing on mind and soul.
But all said and done, its surely something I am happy and proud about.