{"id":20826697,"url":"https://github.com/cedrickchee/doom-wasm","last_synced_at":"2026-04-11T00:46:39.087Z","repository":{"id":247130292,"uuid":"528815226","full_name":"cedrickchee/doom-wasm","owner":"cedrickchee","description":"Porting DOOM to WebAssembly from scratch, without much magic tooling or frameworks (i.e, Emscripten) on our way.","archived":false,"fork":false,"pushed_at":"2022-08-25T11:58:10.000Z","size":1737,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-18T17:49:32.338Z","etag":null,"topics":["doom","from-scratch","javascript","rust","wasm","webassembly"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cedrickchee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-25T11:12:39.000Z","updated_at":"2024-11-01T11:29:44.000Z","dependencies_parsed_at":"2024-07-06T19:47:20.291Z","dependency_job_id":"f8a80fc5-2348-4dea-ae56-3e201177aec2","html_url":"https://github.com/cedrickchee/doom-wasm","commit_stats":null,"previous_names":["cedrickchee/doom-wasm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedrickchee%2Fdoom-wasm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedrickchee%2Fdoom-wasm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedrickchee%2Fdoom-wasm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedrickchee%2Fdoom-wasm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cedrickchee","download_url":"https://codeload.github.com/cedrickchee/doom-wasm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243174000,"owners_count":20248214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["doom","from-scratch","javascript","rust","wasm","webassembly"],"created_at":"2024-11-17T23:09:48.019Z","updated_at":"2025-12-31T00:33:18.921Z","avatar_url":"https://github.com/cedrickchee.png","language":"C++","readme":"# Porting Linux DOOM to WebAssembly\n\nWe got vanilla Linux DOOM starting from Rust.\n\n![DOOM starting via Cargo](imgs/doom_booting_x11_rust.png)\n\n---\n\n## libc\n\nGetting musl\n\n`AR=llvm-ar-10 CC=clang CFLAGS=\"-m32 --target=wasm32\" ./configure --target=wasm32`\n\nno `wasm32` arch support\n\nGetting the arch from https://github.com/emscripten-core/emscripten/tree/efede793113ce1aa4d38d4f2df08e6b251cc53c6/system/lib/libc/musl/arch/emscripten\n\nThrowing out everything which is complicated.\n\nCrossover of musl 1.2.2 and arch from emscripten of musl 1.1.15.\n\nOnly need the string formatting functions anyway. Kicking out everything else.\n\nOnly making `make lib/libc.a`.\n\n## compiler rt for builtins\n\nFrom: https://compiler-rt.llvm.org/\n\u003e For example, when compiling for a 32-bit target, converting a double to a\n\u003e 64-bit unsigned integer is compiling into a runtime call to the \"__fixunsdfdi\"\n\u003e function.\n\nhttps://00f.net/2019/04/07/compiling-to-webassembly-with-llvm-and-clang/\nprovides a precompiled `libclang_rt.builtins-wasm32.a`, which brings down the\nmissing imports to 51. The result looks very promising. But I want to build a\nminimal version myself.\n\nGet https://github.com/llvm/llvm-project/\n`llvm-project/compiler-rt/lib/builtins` sources and compile myself. No need for\narch I hope, no assembly to be emitted. But using git tag `llvmorg-11.1.0`.\n\n---\n\nDat feel! After so much theory and no way to test. Finally seeing the first\nscreen of Doom rendered. Awesome!\n\n![Doom rendering the first screen to an HTML5 canvas](./docs/images/doom_first_screen_renders_to_canvas.png)\n\nDoom rendering broken colors, but can read text:\n\n![Doom rendering broken colors, but can read text](./docs/images/doom_screen_broken_colors_but_can_read_text.png)\n\nStart screen rendering correctly on an HTML5 canvas:\n\nWe mapped Doom's X11 ColorMap to canvas's RGBA color:\n\n![Doom's title screen](./docs/images/doom_titlescreen_html5.png)\n\n---\n\nIf I don't make `I_FinishUpdate` `panic!()`, then Doom runs in its infinite game\nloop. Unfortunately, this runs at 100% CPU, Firefox complains that a website is\nmisbehaving, and nothing is rendered, since the browser has no chance of drawing\nthe animation.\n\nProbably, I want to change Doom such that doom itself is not looping, but I can\ncall the loop via `window.requestAnimationFrame()`.\n\nThis somehow inverses control and gives the browser a chance to render the\nframes.\n\n## Project Structure\n\nA summary of the directory structure:\n- `build.rs`: Rust build script. Tells the rust compiler to build and link to\n  our small libc, compiler runtime, and doom library.\n- `clang_compiler_rt`: C compiler runtime, to compile as static archive.\n- `musl-1.2.2`: libc for C string functions, to compile as static archive.\n- `linuxdoom-1.10`: original doom sources, to compile as static archive.\n- `doom1.wad`: Doom game file.\n- `src`: Rust sources.\n- `index.html`: HTML and Javascript to load the compiled WebAssembly and provide\n  keyboard input and HTML5 canvas rendering output.\n\n## Optimizations\n\nThere is more to optimize for web-native performance.\n\nThe firefox performance profiler says we spend most of our time in\n`gettimeofday`. In\n[b1eab74](https://github.com/cedrickchee/wasm-doom/commit/b1eab74c60776ced95a28d305a16103a0e23c8e7),\nwe remove this implementation completely, avoiding the need to construct a\n`Date` object in javascript and avoiding sedond and microsecond translation,\nsince Doom just cares about the milliseconds since the start of the game, which\nhappens to be what javascript's `performance.now()` provides.\n\nThe game runs at ~35 FPS on my machine, but Chrome performance debugging tools\nstill show many dropped frames, since the browser wants to animate at 60 FPS. In\naddition, since Doom is still polling the time to know when it can proceed, this\nis super energy inefficient and gives the browser no room for background tasks,\nsuch as garbage collection. In\n[a048af0](https://github.com/cedrickchee/wasm-doom/commit/18153c7a048af07a772e54675feb35c349ac0437),\nwe make Doom to return immediately when running one step of its game loop if\nthere is nothing to do, giving control back to the browser. Now, Doom still runs\nat ~35 FPS (this is what Doom was designed for), but the browser gets a chance\nto render its 60 animation frames per second and the system is mostly idle\notherwise. I can clearly hear the difference, since my CPU fan is no longer\nspinning up when starting Doom.\n\n**Canvas**\n\nCopy video bufer one time less.\n\nBefore:\n\n```javascript\nvar doom_screen = new Uint8Array(memory.buffer, ptr, doom_screen_width*doom_screen_height*4);\nvar ctx = canvas.getContext('2d');\nvar render_screen = ctx.createImageData(doom_screen_width, doom_screen_height);\n```\n\nAfter:\n```javascript\nvar doom_screen = new Uint8ClampedArray(memory.buffer, ptr, doom_screen_width*doom_screen_height*4);\nvar render_screen = new ImageData(doom_screen, doom_screen_width, doom_screen_height);\nvar ctx = canvas.getContext('2d');\n```\n\nChrome and Firefox Dev tools confirm that this makes the game run at least twice\nas efficient.\n\n### WebAssembly Specific Optimizations\n\nI want to enable full optimization of the whole wasm binary.\n\nI would love to use LLVM-supported cross-language C/Rust LTO here, but it looks\nlike `wasm-ld-10` does not support this.\n\nThe `-plugin-opt` option is not supported by my `wasm-ld-10` (and it looks like\nneither v11 or v12 support it).\n\nAs reference, `ld.lld` would support this option. IIUC, LLVM currently does not\nsupport LTO for wasm?\n\n```Makefile\nwasm-opt -O3 -o doom.wasm ${BUILDDIR}/xdoom.wasm\n```\n\n---\n\nNow go to https://cedrickchee.github.io/wasm-doom and start shooting monsters!","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedrickchee%2Fdoom-wasm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcedrickchee%2Fdoom-wasm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedrickchee%2Fdoom-wasm/lists"}