‹Back


popdoom

  Source

Screenshot of Popdoom

Note: This looks a lot better live without the compression artifacts and at higher resolution.

About

In 1997, John Carmack released the source code for DOOM for non-profit use. Since then, it’s been ported to any software platform you can think of. In fact, there is a modified version of the DOOM source specifically designed to be ported to new platforms called doomgeneric.

Ever since I learned about this, I wanted to mess around with it, and recently, I finally had an idea that seemed worth pursuing: what would it look like to render DOOM in a pop-art style (because, why not)?

The Implementation

There are several implementations of DOOM that would allow you to change textures or write shaders, but that would skip the fun of binding code directly to the original DOOM source. So instead of doing that, we’re going to write a custom renderer in Rust and link it against a DOOM library that we’ll build.

Building libdoom

The first step is to build DOOM as a static library. With the right compiler flags and a few tweaks, this basically works out of the box with doomgeneric. The cc crate takes care of compiling the C code and linking it into a static library that we can use in our final Rust binary.

Wrapping libdoom in Rust

Since we’re going to be using the C library from Rust, we need to write some bindings. These are pretty easy to write referencing the doomgeneric headers, and we’ll slap a safe Rust API on top of it.

Running the DOOM Game Loop

DOOM is pretty old. It’s no surprise that it has a game loop that is not super compatible with modern application event loops. I chose to build a GPU-accelerated renderer but DOOM uses a software renderer. I can’t just arbitrarily copy the DOOM framebuffer to the screen whenever DOOM tells me to. Instead, we’ll need to juggle the DOOM game loop and our application event loop separately, and shuttle events back and forth between them.

To do this, we’ll run the DOOM game loop completely in a background thread. We’ll use buffered mpsc channels to shuttle events back and forth between the two threads. The main thread will pass input events and control events to the DOOM game loop, and the DOOM game loop will pass framebuffers and other game events back to the main application event loop. When the main thread receives a framebuffer, it copies the texture data to the GPU to be used as an input to the graphics pipeline next time we render a frame.

This works surprisingly well, and is nice and compatible with any platform our winit application event loop supports.

Rendering DOOM

We’ve now got a regular application event loop and the DOOM framebuffer in our hands. The next step will be to copy the framebuffer to the GPU and blit it to the screen’s render target. I’ve set this pipeline up a few times before from scratch using WGPU, but this time I’ll be using a small crate called pixels that takes care of the boilerplate for us. Once we initialize a pixels instance, we’ll get access to the internal WGPU primitives to build a custom render pipeline that will take the DOOM framebuffer as an input texture and render it to the screen.

Hooking this up into our application event loop is straightforward, because this is exactly how it was designed to be used. Bingo-bango, we have the DOOM framebuffer rendered to the screen!

Oh, and now that we can see what’s happening on screen, let’s also send over keyboard events coming in on our application’s main event loop to the DOOM game loop via that channel we set up earlier. There’s only a few keys to handle, but once we do this, we can actually play DOOM. Pretty neat!

Rendering DOOM in Pop-Art Style

Oh man, this turned out to be kinda tricky. I was concerned that DOOM would be too dark and gritty to be rendered in a pop-art style, but I figured the best way to find out is to try it out. There are some good resources online about Ben-Day dots and pop-art style rendering, as well as a couple of simple implementations available on Shadertoy. After banging my head against the wall trying to port some of the reference GLSL implementations in WGSL, tuning the parameters, and experimenting with different blending modes, I finally got something working. I think we can all agree that this doesn’t look amazing. It looks okay-ish, and some elements of pop-art come through, but it’s not quite right and stock DOOM is not the best fit for this style.

There are a couple of things I can think of that may improve this: