Doom on ONNX

mariuz2 pts0 comments

anthonypjshaw/doom-onnx · Hugging Face

Log In<br>Sign Up

Doom on ONNX

A single self-contained ONNX model (doom.onnx, ~8.4 MB) that, when run on<br>any ONNX Runtime CPU EP, boots and renders the original 1993 Doom.<br>No custom operators, no execution-provider plugins, no Python in the loop —<br>just standard ONNX ops (Add, BitwiseAnd, Where, Gather, ScatterElements,<br>Loop, If, …) executing inside a single InferenceSession.run call.

The model contains:

an RV32IM CPU built entirely out of ONNX operators,

the doom1.wad shareware game data as a read-only initializer,

the doomgeneric Doom source cross-compiled to bare-metal RV32IM and<br>baked into RAM as another initializer.

Reference render

The doom.gif in this repo was assembled from 74 PNG frames captured during<br>a single InferenceSession.run invocation:

Total : 80,000,000 RV32IM instructions, 10.8 hours wall time

Rate : 1,562 IPS (init code) → 2,053 IPS (in-game rendering)

Reached : title wipe → menu → DEMO1 load → game logic → 3D BSP<br>rendering of actual gameplay (frames 54–75)

Performance

~2,000 simulated RV32IM instructions per second on a modern laptop CPU.<br>This is not a real-time emulator. One frame every ~9 minutes is the<br>reality on a single CPU thread. See PERF_INVESTIGATION.md in the source<br>repo for the full investigation (TL;DR: ORT's MayInplace alias doesn't<br>apply to Loop-carried state, so the 8 MiB RAM gets fully copied per<br>ScatterElements).

Running

import numpy as np<br>import onnxruntime as rt

sess = rt.InferenceSession("doom.onnx", providers=["CPUExecutionProvider"])

RAM_SIZE = 8 * 1024 * 1024<br>ram = np.load("initial_ram.npy") # Doom ELF baked into RAM<br>pc = np.array(0x1000, dtype=np.int32)<br>regs = np.zeros(32, dtype=np.int32)

MMIO_TICK = RAM_SIZE - 16<br>sim_ms = 0<br>for chunk in range(250):<br>ram[MMIO_TICK:MMIO_TICK + 4] = np.frombuffer(<br>np.uint32(sim_ms).tobytes(), dtype=np.uint8)<br>sim_ms += 100<br>pc, regs, ram = sess.run(None, {<br>"pc_in": pc, "regs_in": regs, "ram_in": ram,<br>"trip_count": np.array(100_000, dtype=np.int64),<br>})<br># framebuffer at RAM_SIZE - 32 - 64000, 320×200 palette indices

The host only writes a millisecond counter into the MMIO tick register<br>between chunks and reads the framebuffer out of the returned ram_out.

Inputs / outputs

Name<br>Type<br>Shape<br>Role

pc_in<br>int32<br>scalar<br>program counter

regs_in<br>int32<br>[32]<br>x0..x31 (x0 forced to 0)

ram_in<br>uint8<br>[8 MiB]<br>full writable memory

trip_count<br>int64<br>scalar<br>how many insts to execute

pc_out / regs_out / ram_out<br>(same types)

post-state

rom (the WAD) is a read-only initializer baked into the model.

License

The CPU + DMA glue is MIT. doomgeneric and the original Doom source are<br>GPL-2.0; the shareware doom1.wad ships under id Software's shareware<br>terms. This model card and model file inherit GPL-2.0.

Acknowledgements

id Software for releasing Doom's source. The doomgeneric project for the<br>platform-agnostic port. The ONNX team for an op set this absurdly expressive.

Downloads last month -

Downloads are not tracked for this model. How to track

Inference Providers NEW<br>Other

This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

doom onnx model single rv32im source

Related Articles