When I started the RigelEngine project, my goal was to offer something similar to modern source ports of Doom and other games: Giving the player a choice to stay close to the original and get an authentic experience (while being easy to run on a modern system), or to enhance the game with some modern features and improvements that wouldn’t be possible on the original hardware. I already had many ideas for these, like the ability to plug in high-resolution graphics or improved sound effects, motion interpolation for smoother movement and scrolling, etc. But there’s one modern enhancement that didn’t occur to me at all: Wide-screen mode. Thanks to Pratik Anand, a contributor who suggested the feature and helped out with the implementation, this is now available in the 0.5.0 release of the game. In this post, Pratik and I are going to talk about the what and why of this enhancement, and some of the challenges involved in bringing a working implementation to the project.

Motivation

Nowadays, most computer screens are “wide”, with 16:9 being the most common aspect ratio. But things were a bit different in the CRT era – which is when the original Duke Nukem II was released. Wide-screen CRT monitors did exist, but to my knowledge, they weren’t very common, at least not among regular consumers (John Carmack used one in 1995, but it cost a small fortune). Instead, most screens had a 4:3 aspect ratio, and that’s what games targeted. Duke Nukem is no exception there.

Running these old games on a modern monitor means that only part of the available space on screen can be used, with “black bars” surrounding the image (also known as pillarboxing):

Extending the image to make full use of the available screen space provides a more immersive experience, and makes the graphics feel properly “in place” with the screen. It’s a matter of preference of course, but for many people, there is simply something missing when playing in a pillarbox mode.

Missing wide-screen support is an issue with many older games, including later titles from the early to mid 2000s. Since many of these games continue to be popular long after their release, and people enjoy playing them on modern hardware, tech-savvy folks have found ways to add some form of wide-screen support even when it doesn’t exist officially. This ranges from configuration file tweaking to patching original binaries using a hex editor. Online communities like the Wide-screen Gaming Forum collect all of these various instructions and patches in one place, and provide a space for discussing wide-screen gaming related topics.

Max Payne 2 in regular 4:3 mode Max Payne 2 in 16:10 mode, with an unofficial patch

For games that had their source code released, the situation is much better, since wide-screen support can be implemented natively without resorting to hacks (some examples: Doom, System Shock, Quake III Arena). And of course, that’s also the case for RigelEngine. Nevertheless, it wasn’t a trivial change. Let’s have a look at what was involved.

Implementation



On a conceptual level, the change seems straightforward: Extend the visible area to draw more of the level and move the HUD to the right of the screen, as shown on the following screenshots:

But it turned out not to be quite as simple as that, since there are multiple additional considerations.

Active region

Duke Nukem II, like most 2D side-scrollers of its time (1993), limits updating of game objects to only those that are visible on screen. In RigelEngine, this is handled by the entity activation system, which marks entities as being active or inactive at the beginning of each frame. This system now needs to be aware of the current view port size, in order to know which objects need to be activated. Otherwise, you would see frozen enemies and shots disappearing near the edges of the screen, as shown in the animation below:

Camera

The camera determines which part of the map is visible, and how the view moves along with the player. This works by defining a “dead zone” area, inside of which the player can move freely without affecting the camera. As soon as the player moves too far, the camera follows to keep the player inside the dead zone:

The logic behind this camera movement has been reverse engineered from the original assembly code, and was designed to target the original game’s (fixed) view port size. In principle, it would be possible to extend the logic to handle varying view port widths. Instead, we decided to keep the dead zone fixed, but centered in the middle of the screen. This results in the same camera behavior for regular and wide-screen mode.

One implication of this decision is that the game becomes somewhat easier to play in wide-screen mode, as enemies can be spotted (and shot at) sooner. Extending the camera dead zone to cover a larger part of the screen in wide-screen mode would counteract this. But since not being able to spot enemies soon enough is a common complaint with the original game, it felt right to make wide-screen mode a little easier. One can always turn off widescreen mode to enjoy the vanilla Duke Nukem II experience.

Up-scaling

The original game only supports one screen resolution: 320×200. On a CRT monitor of the time, the image is going to be stretched vertically to produce a 4:3 aspect ratio, notably resulting in non-square pixels (a little taller than wide). On a modern machine, on the other hand, we’ll typically find a LCD screen with at least 1080p resolution, and square pixels. With that technology, it’s best to render at the display’s native resolution for ideal quality. This means that the original pixel art needs to be upscaled to a much higher target resolution, while reproducing the vertically stretched look provided by non-square pixels on a CRT as best as possible.

Because of the way up-scaling was done at the start of the project, quite a few changes were required in order to implement wide-screen mode. Back then, I picked the simplest approach possible, knowing that it’s going to require changes later on due to its limitations. It seemed preferable to focus on building up the game’s core functionality first, and tackling more flexible rendering later.

In the old approach, the whole game was rendered into a 320×200 texture, which was then drawn onto the screen with the right scaling to fit the largest available 4:3 sub-section of the screen. The following diagram illustrates this:

This approach made it very easy to write rendering code, because I could pretend that my screen resolution was actually 320×200, and write all the code accordingly. But as I already mentioned, there are limitations:

Everything is scaled by the same amount, which makes it impossible to integrate optional higher resolution graphics alongside original art assets

In order to support different aspect ratios, as needed for wide-screen mode, we would have to recreate the render target each time we switch between wide-screen and regular mode, or when changing the window size.

In addition to these inherent limitations, the code was also structured in a fairly inflexible and somewhat confusing way, making it harder to apply the changes necessary for wide-screen mode regardless of the approach taken. Therefore, I decided the time was right to do a larger overhaul of the up-scaling pipeline, clean things up, and implement a new approach.

Instead of rendering everything into a small buffer and then scaling the resulting pixels, we now set a transformation, which makes the renderer scale each individual texture as it’s being drawn. This nicely solves both restrictions:

We can apply different transformations for different textures, enabling us to mix graphics of various resolutions

The scaling works without a render target now, so there is no need to manage or recreate one

Notably, we still need to use a render target in order to draw particles from explosion effects: Since these are rendered as single pixels, using a render target is the easiest way to recreate the non-square pixel look of the original (otherwise, we’d have to draw small rectangles instead of single points). But this is limited to a specific part of the code, and most of the rendering now works using only transformations for upscaling.

Remaining issues and future work

We are quite happy with how wide-screen support turned out, and it’s a lot of fun to experience the game again in a new way. Still, there are more things that could be done.

“Holes” in the map

One problem is that originally, the game’s HUD used to cover up the entire bottom part of the screen. Some levels are designed in a way that the bottom-most tile of the map can be at the bottom of the screen, which looks fine in regular mode, but leaves a “hole” when wide-screen is enabled:

In regular (4:3) mode, the HUD covers the entire bottom part of the screen, hiding the fact that there are no more tiles below the one Duke is standing on. In wide-screen mode, the HUD is shifted to the right, revealing the empty part of the level below Duke’s position.

For now, we render nothing in these spots, making the background show through, which looks kind of broken. It would also be possible to draw black instead, or keep repeating the last tile value. Extending the HUD to be longer would also be an option. It would be good to do some experimentation and see which looks best, maybe even offer the player a choice in the options menu.

Configurable HUD

Even in wide-screen mode, the game’s HUD takes up a lot of space. It was most likely originally designed to be this big, in order to reduce the amount of pixels that had to be transferred to the graphics card each frame – a common technique with games from the 80s and 90s, which had to work with very limited hardware.

But on a modern machine, this is not an issue at all. Thus, it could be interesting to add an option to make the HUD smaller, or even implement an alternative HUD with a much more minimal display. This would make it possible to show even more on screen.

Revealed secrets

Since the player can see more of the level on one screen now, there are some cases where parts of the level become visible that are meant to be hidden. A good example is at the beginning of L3, the 3rd map of the 1st episode. There is a shootable rock floating in the air, which can be shot to fall down and allow the player to reach an otherwise unreachable area:

Normally, this rock is out of view, making it a secret the player has to discover. But in wide-screen mode, it’s partially visible:

It’s not immediately clear if and how this could be prevented, but it could be interesting to think about an option for keeping secrets hidden. At the same time, it’s questionable whether that’s worth it. Again, the vanilla mode is always just a click away. Some source ports of Doom add optional features like Jumping that didn’t exist in the original game, and these features do allow reaching secrets more easily as well.

Configurable camera and active region

As mentioned earlier, the behavior of the camera currently remains unchanged for wide-screen mode, whereas the active region is extended to cover the entire screen. It could be interesting to have alternative modes. For example, the active region could stay the same as in regular mode, maybe with some kind of “fog of war” effect to cover the areas of the screen that would be outside the active region. Another mode could change the camera to adapt to the wider view port as well. This would offer a lot of flexibility to the player, for experiencing the game the way they like it.

Contributions welcome!

If one of the areas for future work mentioned above sounds like something you’d want to work on or contribute to, you’re more than welcome! There is some info on getting started in the README, and you can also find Pratik and myself in the project’s Gitter chat room, where any kind of question is welcome.

With that, we’ve reached the end of this post. We believe we’ve added a nice enhancement to the game with this new mode, and hopefully we could give some insights into the work that went into it. And now it’s time to get the latest build and try it for yourself!