Doom rasterized on XDNA: an intrusive thought, a composable compiler stack, and ten resident NPU workers. Video, spatial diagrams, C++, Loom IR, native Low, and measurements. A developer added native XDNA compilation and experimental execution to the Loom compiler stack, then used it to run Doom with the game's world rasterized by ten resident NPU tiles on a Ryzen AI Max+ 395. The C++ pixel worker, imported through Loom's C/C++ frontend, uses fixed-point integer arithmetic and zero vector or matrix instructions, with a controlled replay of one captured frame completing the native submit/wait path, including DMA, in 0.53 ms median. The CPU handles game logic, visibility, and draw-command preparation, while the GPU presents the resulting image. Next: Quake on XDNA https://gist.github.com/benvanik/f6b8206990a1c8e24e74611d8d63334b — sixteen cooperating NPU tiles, texture paging, vector rasterization, and the path to CPU→NPU→GPU streaming. Three weeks ago, Loom had no XDNA backend. On September 17, native XDNA compilation and experimental execution landed in main. On September 18, we were playing Doom with its world rasterized by ten NPU tiles, using a C++ pixel worker imported through Loom. The idea began as a joke. The speed came from being able to compose substantial pieces we had already built: a compiler with reusable analyses and explicit hardware models, a native device-access library, and now a C/C++ frontend. An unexpected application gave those pieces a chance to meet. Live play on a Ryzen AI Max+ 395. The recording switches between latency measurements and screen ownership, physical NPU placement, and each worker's draw-command history. The window is forwarded over Waypipe; NPU timings are measured on the rendering machine. The CPU runs the game, determines visibility, prepares draw commands, and draws the status bar and menus. The NPU samples textures, applies lighting tables, and writes the world pixels. The GPU presents the resulting image and diagnostic overlay. Walls, floors, ceilings, sprites, the weapon, and Doom's wonderfully peculiar fuzz effect all go through the NPU worker. The CPU world rasterizer is disabled during normal play; reference rendering is an explicit checking mode. The worker uses fixed-point coordinates, integer arithmetic, byte texture reads, and byte color tables. It emits zero vector or matrix instructions . Ten scalar programs, each occupying 1,330 bytes of program memory , do the drawing. A controlled replay of one captured frame completes the native submit/wait path, including DMA, in 0.53 ms median . Preparation and display have separate costs. XDNA is an array of programmable processors with local memory and communication resources. Its AI Engine tiles include scalar and vector processors for AI and signal processing. Exposing computation, memory, and communication gives that hardware a broad space of programs to execute. AMD's XDNA architecture overview https://www.amd.com/en/technologies/xdna.html Loom is a compiler stack for expressing programs and mapping them onto machines. The C++ importer https://gist.github.com/benvanik/274b1fdb5201b3bc6827b17f62be9241 is one entrance to it. For this experiment, we extracted Doom's ordered rendering commands, divided them into bounded packets, expressed a pixel algorithm, and chose a spatial work distribution. Those application decisions are visible in the program. The importer owns language semantics. Built on the standalone cxx parser and semantic frontend, it translates C++ types, expressions, loops, and buffer access into Loom High IR. The result remains structured and editable. The companion C++ → Loom writeup https://gist.github.com/benvanik/274b1fdb5201b3bc6827b17f62be9241 shows this boundary and the GPU side of the story. Importing the raster algorithm required no knowledge of NPU tile coordinates, DMA channels, or instruction slots. Loom owns reusable compiler machinery. Analysis, canonicalization, dependence scheduling, register allocation, and pass lifecycles operate on common representations. Types, ranges, memory effects, and control flow survive the handoffs. This is what lets a new frontend feed an unfamiliar target without rebuilding a compiler between them. The XDNA backend owns the machine model. It supplies legal instruction forms, register geometry, issue constraints, storage and routing rules, and native encoding. The array planner assigns workers, SRAM, DMA, locks, and routes. Loom emits the complete native image—including configuration, bindings, and invocation records—in process. The backend handles an ordinary integer program with explicit communication; Doom adds no special compiler operation. libamdf owns native device access. Its C API supplies discovery, memory and addresses, visibility, context admission, submission, and completion. The experimental loader above it interprets the image, binds storage, and retains the execution resources. On Linux, libamdf uses the amdxdna kernel driver and firmware. This separates platform access from executable loading and scheduling policy, while keeping the resource lifetimes explicit. These boundaries are APIs and representations that can be developed and tested independently. The small deployment unit contains substantial engineering: a frontend, optimizer, scheduler, allocator, native encoders, spatial resource planner, executable contract, and device-access path. The experiment could reuse each at its proper boundary. A new frontend and a new backend met in the middle. The intrusive thought became an application. An optimized, stripped Loom compiler with AMDGPU, x86, XDNA, SPIR-V, and LLVM IR enabled occupies 12.75 MB . Adding XDNA to that configuration accounts for 1.34 MB . This is the compiler executable; the optional C++ importer and system libraries are separate. In a resident JIT study, specializing and emitting complete native images for a small BF16 gate/up workload takes about 1.8 ms on XDNA , alongside about 1.7 ms on AMDGPU using the same shared compiler. That interval starts with parsed source and an initialized compiler. These are compiler measurements from separate workloads; Doom's 0.53 ms above measures execution. A separate matched experiment compared fresh compilation of a small streaming I8 matrix program through Loom and the open IRON/MLIR-AIE stack: | Fresh source → complete native image | Loom | IRON / MLIR-AIE + Peano | |---|---|---| | Median wall time | 2.78 ms | 2.145 s | | Executed processes, including public driver | 1 | 8 | | Measured compiler deployment footprint | 12.17 MB | 285.63 MB ¹ | September 13 compiler snapshot; one warmup and nine alternating measured runs, warm OS caches, fresh output directories, benchmark lease held, no finished-artifact cache hits. Both sources describe one persistent worker, three depth-two channels, and 64 signed I8 M8N8K8 products. The SDK used its supported inline-kernel option. ¹The SDK figure counts invoked executables and their SDK shared-library dependencies after stripping and deduplication; Python, its modules, headers, target archives, and system libraries are excluded. It is a conservative measured subset, not a minimum deployment claim. Both expose one public compile invocation. Inside the measured SDK path, Python drives C++ compilation and aiecc ; LLVM optimization, code generation, linking, and image packaging execute through opt , llc , clang , ld.lld , and aiebu-asm . Reusing the generated MLIR and C++ LLVM IR reduces that SDK run to 92.6 ms , which identifies a different, narrower measurement boundary. The source-to-image difference includes frontend work. This study compiled and inspected the SDK artifact; it establishes no SDK hardware-performance ratio. Measured SDK release https://github.com/Xilinx/mlir-aie/releases/tag/v1.4.3 · Pinned Peano dependency https://github.com/Xilinx/mlir-aie/blob/v1.4.3/utils/peano-requirements.txt Version coordination is also part of deployment. The upstream setup explicitly matches compiler wheels to the source checkout and warns about mismatches. Loom's compiler components and native-image contracts ship together, while the frontend and device-access APIs remain distinct. Upstream installation requirements https://github.com/Xilinx/mlir-aie/blob/main/README.md The practical result is a short experiment loop: change an algorithm, layout, or work distribution; compile the actual program; inspect what the machine will receive; and measure it. A bad idea can be rejected cheaply. A promising one can be refined without changing software stacks. Doom's 320 × 200 image becomes twenty full-height strips, each 16 pixels wide. Ten resident workers process two strips apiece. Full-height ownership keeps the fuzz effect's vertical neighbor reads inside one worker's output, preserving draw order. The CPU clips and bins commands and packs the original texture data into bounded records. The distinction between compute placement and communication footprint matters. Ten workers fit in three compute columns; the chosen external routes require a five-column context. A worker-count expression alone cannot describe that resource cost. The compiler's plan makes it inspectable. The actual spatial composition pipeline.def