Optimizing RigelEngine’s OpenGL rendering for the Raspberry Pi

While working on RigelEngine, performance was mostly a non-issue. Modern computers are so powerful that a 2D game from the early 90s is no match for them. My gaming PC easily runs the game at well over 3000 FPS when uncapping the frame rate, and a 6 year old MacBook Pro still runs at more than 400 FPS. But it’s a different story on the Raspberry Pi. When I first tried running RigelEngine on a Pi 3 model B, it only managed around 15 to 17 FPS when in-game. After a round of optimizations, it now runs at a stable 60 FPS (v-sync on) at 1080p resolution on both Pi 3 and Pi Zero. In this article, I’ll go through the optimization process and describe the changes that made a big difference in performance. And since the project is open-source, you’ll find links to all the relevant code changes.

RigelEngine running on a Pi Zero, with a Pi 3 and Pi 1 sitting next to it
Continue reading “Optimizing RigelEngine’s OpenGL rendering for the Raspberry Pi”

RigelEngine on CppCast

I had the honor of joining Rob and Jason for last week’s episode of CppCast to talk about RigelEngine! Listen to the show here or watch it on YouTube:

In this post, I’d like to expand on some of the topics we discussed.

First, some links for a few things I mentioned:

Continue reading “RigelEngine on CppCast”

RigelEngine v0.7.0 released

This release adds keyboard control rebinding, a new quick save feature, and a few other missing features. On top of that, some bugs and inconsistencies with the original game have been fixed. Also worth noting is that game options are now stored in a human-readable text file, next to the user profile file. Check out the full release notes on GitHub.

Continue reading “RigelEngine v0.7.0 released”

RigelEngine v0.6: Registered version support, new features

It’s been a long time since my last update on RigelEngine, but that’s not due to nothing happening in the meantime – quite to the contrary! So let’s have a quick look at what the project has been up to.

The first big news is that as of version 0.6.0, the entire game (including the registered version’s content) is fully playable without any game-breaking bugs or major inconsistencies. This is a significant milestone, as it means the project is now feature complete as far as gameplay goes. But feature parity wasn’t the only area where progress was made: Usability is another one, with quite a few nice improvements and additions. And finally, a ton of bugs and inconsistencies with the original have been fixed, making the game more robust and playable overall.

Continue reading “RigelEngine v0.6: Registered version support, new features”

RigelEngine: Adding a wide-screen mode

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.

Continue reading “RigelEngine: Adding a wide-screen mode”

RigelEngine: A quick update

It’s been more than three months since my last post, so I wanted to give a quick update on what has happened in the meantime. I didn’t have a ton of time to spend on the project, but it’s slowly moving forward. My focus right now is mainly on two things:

  1. Adding more documentation/comments, and doing cleanups to make it easier to get into the code base and contribute to the project. As a neat side-effect, this has also given me a lot of new ideas for refactoring and restructuring the code.
  2. Improving usability: Options menu to control sound and music volume, switch between windowed and full-screen mode, etc., to provide the level of configurability expected from a PC game. There are also some plans to automatically download the (shareware) game data.

I’m quite happy to see more people using RigelEngine now, which has led to a lot of improvements to the README and build process thanks to various contributors. In addition to these improvements, there have been two major code contributions:

Continue reading “RigelEngine: A quick update”

Space Game: A std::variant-Based State Machine by Example

At last year’s Meeting C++ conference, I gave a talk called std::variant and the Power of Pattern Matching. Earlier this year, Bartłomiej Filipek asked if I’d be interested in writing a guest post for his blog, and of course I said yes! So now, here it is: Space Game: A std::variant-Based State Machine by Example.

The post focuses on implementing state machines using std::variant and the little space combat game I wrote for the talk.

Re-implementing an old DOS game in C++ 17

Back in 2016, I started a side project to reverse engineer the game Duke Nukem II and build an open source reimplementation of its engine from scratch – called Rigel Engine (Check it out on GitHub). Now, more than 2 1/2 years later, my version is complete enough to allow playing the entire shareware episode of the original game, offering a practically identical experience to running the original. Here’s a video showing off the first level:

So what can it do? Rigel Engine works as a drop-in replacement for the original DOS binary (NUKEM2.EXE). You can place it into the game directory and it will read all the data from there, or you can specify the path to the game data as a command-line argument. It builds and runs on Windows, Mac OS X, and Linux. Based on SDL and OpenGL 3/OpenGL ES 2, written in C++ 17.

It implements the game logic for all the enemies and game mechanics found in the Shareware episode, plus most of the menu system. Saved games and high scores from the original game can also be imported.

On top of that, it already offers some enhancements compared to running the original game:

Continue reading “Re-implementing an old DOS game in C++ 17”

RAII with C libraries: Follow-up

Last week, I talked about the custom deleter feature built into std::unique_pointer, and how we can leverage that to easily add RAII-support to C-libraries. As it turns out, there are alternative ways of achieving what I showed last time, which are simpler and have some other benefits (thanks to Micheal who pointed that out to me in the comments!). So let’s take a look at these and discuss the differences to the approach that I’ve shown previously.

Using a functor instead of a function pointer

We were using function pointers to specify how the pointer managed by a std::unique_pointer should be deleted. This is not a requirement though, any C++ type that’s callable with the pointee type as argument can be used. So instead of a function pointer, we can define a deleter functor with an overloaded operator() for each type we want to support:

struct Deleter {
void operator()(SDL_Window* ptr) {
SDL_DestroyWindow(ptr);
}
void operator()(SDL_Renderer* ptr) {
SDL_DestroyRenderer(ptr);
}
void operator()(SDL_Texture* ptr) {
SDL_DestroyTexture(ptr);
}
};

view raw
deleter.cpp
hosted with ❤ by GitHub

So far, this isn’t too different from the mapping function we used last time, but it allows us to get rid of our custom subclass for std::unique_pointer, instead replacing it with the following simple type alias:

template<typename SdlType>
using Ptr = std::unique_ptr<SdlType, Deleter>;

view raw
ptr_def.cpp
hosted with ❤ by GitHub

There is no more need to specify the function pointer value when constructing the std::unique_pointer, because our functor type is default-constructible. Therefore, it’s sufficient to make the type known to std::unique_pointer, and it can instantiate the functor itself based on the type alone.

So this makes our code simpler, but there are more benefits: Since our functor type doesn’t have any members, it effectively doesn’t require any storage. This means that our std::unique_pointer will still be just as big as a raw pointer would be, whereas it would require additional space to store the function pointer’s value before. The other advantage is more theoretical in my opinion, but it’s still good to know: With the functor approach, the compiler can very easily see the concrete code that’s going to be executed when our smart pointer goes out of scope. Therefore, if it’s possible to inline the call to the destruction function, it will be easy for the compiler to do so. With a function pointer on the other hand, the compiler would need to know the value of that pointer at the time it needs to invoke it, which can be harder to keep track of in nontrivial code. I say this is more of a theoretical aspect, because the destruction functions we are dealing with here are coming from libraries, so it’s very unlikely that it’s actually possible to inline them (unless the library provides their definition in a header file). Still, it’s clear that the functor approach gives the compiler more and easier opportunities for optimization, so it’s definitely preferable.

Allowing more flexible code structuring

Using a functor simplifies our design, and also has potentially better performance characteristics. But there’s more we can change to make the whole approach more flexible. With the solution presented last week, as well as with the functor design shown above, we have to provide all our type-to-deleter-function mappings in a single place, e.g. a single header file. If the library we are working with is fairly large, or we want to use several different libraries, this might not be desirable. So let’s have a look at ways we can restructure our design to allow distributing the mappings over multiple files:

template<typename LibraryType>
struct Deleter {};
template<typename LibraryType>
using Ptr = std::unique_ptr<LibraryType, Deleter<LibraryType>>;

view raw
ptr_def_generic.hpp
hosted with ❤ by GitHub

Our functor has now become a template, and the type alias for our smart pointer is providing the template argument for it by specifying Deleter<LibraryType> as the deleter type. On its own, this isn’t of much use, since it won’t even compile yet (as there is no call operator in the Deleter template). But now we can put that generic code in a utility file somewhere, and add all required type mappings in additional files by making use of template specialization to define the right call operator for each library type. For organizing those files, we can choose a granularity that best fits our library or libraries and the way we use it/them. Let’s see how to specify the mappings for our already known SDL example types:

template<>
struct Deleter<SDL_Window> {
void operator()(SDL_Window* ptr) {
SDL_DestroyWindow(ptr);
}
};
template<>
struct Deleter<SDL_Renderer> {
void operator()(SDL_Renderer* ptr) {
SDL_DestroyRenderer(ptr);
}
};
template<>
struct Deleter<SDL_Texture> {
void operator()(SDL_Texture* ptr) {
SDL_DestroyTexture(ptr);
}
};

view raw
sdl.hpp
hosted with ❤ by GitHub

This is a bit more verbose than having all operator overloads in a single functor, but that’s the tradeoff we have to make in order to get more flexibility. We could place each single one of the template specializations shown above into an individual header file for example, which wouldn’t be possible with the simpler approach. If we want to use a more complex library like OpenSSL, which has many different header files, we could easily add a wrapper header of our own for each of the library’s headers, and define our deleter function mappings in there.

One disadvantage I see here is that we lose the ability to show a clear error message in case someone tries to use our pointer with a type for which no deleter specialization exists. With the single mapping function, it was easy to add a static assert for the case that the type isn’t known, but this isn’t possible in the same way with the functor approach. This won’t be important when everyone working on the project is familiar with this machinery and has good C++ knowledge. But I like to make my code friendly to use even for people who are maybe less experienced or knowledgeable about every area of a code base, so I’d like to think about this some more and see if there’s a way to bring back catching errors like this.

Conclusion

We looked at how to improve our design from last time using functors instead of function pointers. There are several benefits: Not only is the functor-based design conceptually simpler and potentially easier to optimize, but it also allows us to distribute the deleter-function mappings across several files as needed. Note that this would have been possible as well with the C++ 11/14 approach shown last time, but the functor approach works in those versions of the language too, and is much simpler. What we lost on the way (for now) was the ability to easily catch errors and provide a custom message indicating how to fix the issue, but it seems a worthwhile tradeoff to make, and there should still be ways to bring back improved error messages.

State of the Project

I was originally planning to write this post shortly after the last one, but oh well – as these things go sometimes, I was busy with other stuff and didn’t get around to it 🙂 But, here it is finally. In the meantime, I’ve made a lot of progress on the project itself, and also gave a talk about it at a local C++ meetup. Now, let’s go back in time and have a look at how the project started.

A bit of history

The first commit to the public GitHub repository was on the 22 October 2016, but that was just a squashed copy of the private repo I was using before. The pre-GitHub history consists of another 247 commits, with the first one being on the 21 August. But taking that as the start of the project wouldn’t be entirely true either: That first commit already had about 1.6k lines of code. Before starting with Rigel Engine, I was working on another project, which was about making a level editor that would work with various Apogee games. I never got very far with it before abandoning the idea, but I reused some of the asset loading code for Rigel Engine. Not all of my work on the editor was focused on Duke, but it seems fair to say that the first pieces of code for the project date back to somewhere around the beginning of August 2016, where I was working on that editor.

Continue reading “State of the Project”