How the FNA Project Works
Yep, that's right, another big post about an open source project in response to the recent Unity news! I don't even need to link to the news at this point...
There has been nonstop activity around FOSS game development this past week, which has been wonderful to see - I am particularly happy to see Godot's success during this time. Many smaller projects have gotten mentioned as well, including LÖVE, MonoGame, and of course FNA. Developers have been sharing what they can by advertising these projects, especially so that users panicking over Unity's fees know that there may be a way out for them. Some of it has been accidental (I still can't believe Unity handed Godot a free fundraising campaign on the day that their funding program launched), while others have been sharing various resources for learning new tools, migrating away from old tools, and finding new assets and resources that integrate well with their new tools.
The FNA project has these sorts of things too, and while it's been fun to joke at Unity's expense, I've been pretty careful to not overtly advertise FNA during this time. The reason for this is that I did not want to give developers the wrong impression of the project, but at the same time, I have to deal with the issue that people who are interested may not have a good sense of what FNA actually is and how the project works. This write-up exists to answer the second question: How is FNA made, and how will it not just become another Unity?
This post is also an attempt to introduce some added transparency to our process, since many new faces are looking at FNA after we received an extremely generous donation from Re-Logic, the studio behind Terraria! You can find their announcement here. Once again, the team is very grateful for this contribution, and I thank them for motivating me to finally get all of this written down.
About FNA
On paper, FNA is a reimplementation of XNA 4.0, focused on runtime accuracy and portability across any platform that has a .NET CLR (be it Microsoft .NET, Mono, or whatever). If you want to know the technical details about the library itself, the wiki is a good place to start.
But again, this write-up is less about the library itself, and more about how it's made. On the surface, it's primarily developed by myself, with a small group of core developers and a handful of community contributors stopping by every now and then. Because our project is about replicating what is effectively a dead library, our workflow is designed less for speed and more for consistency; FNA itself does not get very many commits in 2023, but we still do a release on the first of the month, every month, and have done so since January 1, 2016!
As an example: Here are the commit counts for all of the 2023 FNA releases:
- 23.09: 2 commits
- 23.08: 2 commits
- 23.07: 2 commits
- 23.06: 5 commits
- 23.05: 6 commits
- 23.04: 4 commits
- 23.03: 2 commits
- 23.02: 9 commits
- 23.01: 3 commits
So the last 9 releases since December of 2022 have had... 35 commits. Compare that to Godot, which recently merged 28 pull requests within a 48-hour period!
So what are we doing with our time then? Where is all our funding going, and why would we need more of it?
What is FNA Really?
The truth is, FNA is one developer's tool to do one very specific thing, which happens to be a thing that many others need, and so it is shared with the community accordingly. I would emphasize the word tool, which I am carefully picking over the word product; the latter isn't a bad word necessarily, but they are very different at their core. A tool is just a means to an end, while a product is the end in itself. The Godot team, for example, focus on making a FOSS game development product, one with which they can build commercial services on top of (whether it's repo subscriptions, enterprise contracts, whatever). And that's totally okay!
FNA is different in that no developer, myself included, has made FNA their one and only job, and that's very intentional. The idea is that everybody works on their own thing, and oftentimes it will involve using FNA, which means that inevitably users will need to fix issues, improve existing features, or in rare cases add new features entirely. I do a lot of work on Linux ports using FNA, some use FNA to make PC versions of their old XBLIG games, some make new games with it, the list goes on. But nobody's really going around selling FNA necessarily.
This is really important to us for a number of reasons:
- No pressure to keep the line going up. If FNA was the end itself, somebody would have to go out of their way to keep FNA growing in market share, possibly at any cost - this doesn't make sense for a library whose primary goal is to be really really old. (So no, we're not about to add Nanite support to XNA...)
- It allows FNA to be very opinionated about its goals. There isn't any pressure to compromise on the one very specific goal that we have, which is to preserve XNA. If you like XNA, great, if not, FNA isn't for you, and that's 100% okay. We make no claim to be at the cutting edge when our job is to mimic a library that is essentially a frontend for Direct3D 9 and a pile of long dead Microsoft middlewares (Effects, XACT, .NET 4.0...)
- FNA can basically never go away, because it's not architected to have a single point of failure that even some FOSS projects can be prone to. We've made sure to build XNA preservation on top of a technological foundation that is much bigger than FNA itself, allowing for easy maintenance for years to come, no matter how old the XNA specification gets.
On the flipside, this also holds us to a much higher standard when it comes to doing the one thing we're supposed to be doing. If we screw up XNA preservation, we can't point to a board of directors or shareholders, it's our mistake and we have to take responsibility for it. Despite having a largely flat and fragmented organizational structure, there is still an accountability system and that's really important to have in an industry where it seems like nobody is accountable for anything anymore! (Don't sue us though, be sure to read the license, especially that "no warranty" part!)
Take my own catalog, for example - I maintain ports of over 50 games using FNA, which expands to nearly 150 SKUs, all of which I still maintain to this day. If we do so much as a single thing that adds even the slightest inconvenience, I get to personally experience that inconvenience over a hundred times at once! Everyone with a stake in FNA risks facing similar consequences, so we take our work very seriously when it comes to doing Our One Job(TM).
What Does the FNA Team Do?
Okay, I'll finally answer this, and I'll answer it quickly:
- We implement XNA as accurately and robustly as we can
- We contribute to libraries that do all the stuff FNA needs that is NOT XNA-related
People have often called XNA "low-level", which I guess is true relative to a lot of what's out there, but even XNA has lots of moving parts and a lot of them really aren't unique to XNA at all. We have made it a point to share as much of this as possible, because unlike the world of proprietary software, where it's an arms race for who can gather the best middlewares, the Free Software community can share critical infrastructure so that we can focus less on the boring details and more on the interesting ones!
This is also how we're able to contribute to FNA without having it be only FNA work; much of what we develop is for upkeep of our own separate projects, but by adopting FNA's policies on how the technological foundation for FNA is developed, it ensures that the project remains relevant as the environment changes, even when FNA's goal is ultimately not to change at all.
Here are some examples of what we've contributed to, as well as some stuff that we made from scratch:
SDL
SDL needs no introduction at this point. We use it as our platform layer in FNA; FNA contains absolutely positively zero OS-level code in it, all our platform work goes directly into SDL. This is how we're able to have no NDA'd code in FNA - so yes, the FNA repo you see on GitHub is exactly what ships on the Nintendo eShop right now!
As a result, the FNA project has contributed many features to SDL. Some were just minor patches and some were pretty major features; here's a small selection of my own contributions:
- A way to get the default audio device and its wave format
- Support for setting the LED color on PlayStation controllers
- Support for the Wii U GameCube controller adapter
- The revived Wayland video driver
- Google Stadia support (yes really)
Again, this is just a small set of fun examples - the FNA core team continues to work on SDL in our day-to-day maintenance of FNA, particularly for console ports which aren't able to get as much attention due to them being hidden behind NDAs. Putting our efforts into SDL allows our work to be used by other projects, whether they use FNA or not, and others' contributions come back into FNA in the same way. When I do SDL work for Unreal Engine 5, for example, that work ends up being for FNA too!
FAudio
FNA's implementation of XAudio2 and XACT, which are used for XNA's audio support, is called FAudio. Given that XAudio2 was separate from XNA, it made sense to make FNA work the same way. It now powers many games directly, including The End is Nigh, Skullgirls, and Stardew Valley. It's also the XAudio2 implementation in the Apple Game Porting Toolkit!
Oh yeah, did I mention that it's installed on every Steam Deck right now?
In addition to contributing XNA 4.0 support for Proton, we also provided XAudio2 support as well - the FNA project was actually the main vehicle for fixing audio for a huge catalog of Windows games running on Linux, including Halo: The Master Chief Collection, The Elder Scrolls V: Skyrim, and Final Fantasy XIV!
I think it's worth reminding here: While I continue to advocate for native Linux versions of games (which FNA and FAudio make easy!), FNA is able to maintain its own goals by allowing its usefulness to be applied towards running non-native games, and the Wine team have become excellent contributors to both FNA and FAudio in return. Had FNA not had that separation between itself and its authors, that opportunity might have been lost!
NativeAOT
.NET on consoles is... not a fun story. JITs aren't allowed on consoles, and the AOT ecosystem for .NET has never been particularly great. Up until recently, the only tool that came close to providing a shippable toolchain for C# games on console was BRUTE, but as you might expect, building a privately-shared compiler for basically all of .NET, as a small community of indie studios with limited resources, was a difficult situation.
NativeAOT changed that, and rather quickly: It was a fully-supported, fully open workflow to make native binaries that could feasibly get through a console certification process. And that's what the FNA team did: You can now get a NativeAOT workflow for all three major consoles, two of which have already shipped a number of commercial games!
But remember, this isn't just about FNA, and we're sticking to that procedure. With Godot now on the way to consoles, we're very eager to be a part of their efforts to bring C# support to Godot on consoles as well, given that we have a working system that should be reusable by other projects (and where it's not reusable, we plan to fix that). Our long-term goal is to upstream all of the console workflows so that all .NET developers with console access can easily get this work and use it for their own projects, FNA or not.
... and so much more!
As you can see, while FNA has a quiet exterior, on the inside we do a lot and we try to share it with as many projects as possible. Whether it's fixing minor bugs in SDL or adding OpenGL support for Xbox or even OpenGL ES support for Stadia (again, really), there's a lot involved in making XNA work that doesn't really involve XNA. But of course, this is what we were working on...
What Does the FNA Team NOT Do?
We're big advocates for preserving the XNA runtime - what we're not focused on is XNA's development pipeline. Generally we take the stance of "this is great if you know C# and know how to build .NET stuff, otherwise we encourage you to either learn these things first or learn something that will be more friendly to new users." We're more than happy to endorse Godot, for example, as they have a far superior learning curve and do a lot more things for developers when it comes to the content pipeline - in contrast, we actually make a point to get developers off the XNA content pipeline because it's bad and smelly and I hate it. (You can actually tell I dislike it because unlike this post, my XNA Content write-up has lots more naughty words in it, so... just a heads up if you click on that!)
So, if you're looking for an all-in-one experience, we may not be the best fit. If you're okay with making your own tools, FNA and our subprojects will likely fit in very well!
Speaking of subprojects:
What is the FNA Team Doing Now?
If you were to become a sponsor right now, this is what your contribution would be going towards:
SDL_ActionSet
SDL_ActionSet is my top priority at the moment; there are input libraries like Steam Input that use action sets but they're all proprietary, so somebody needs to make a FOSS implementation. While XNA does have its own input API, we think that input can be done better, and that developers should be able to improve their user input and accessibility features, without being forced to use only XNA's input API (which is, as you might expect, dated and imperfect in 2023).
We'll keep preserving XNA Input as best as we can, and SDL_ActionSet will do a lot to make that easier while also improving the situation for new games too.
SDL_StorageContainer
One area where SDL falls pretty short is storage: For the most part SDL has not really gotten involved with the filesystem, with very rare exceptions. The problem is that platforms have dramatically changed how applications access the filesystem to begin with; there is now a distinct difference between "title" storage and "user" storage, even on PC! For consoles in particular, this can make life difficult when porting, because it's often the one thing that SDL can't currently help with at all.
SDL_StorageContainer aims to fix this problem. As it turns out, this is one API in XNA that actually aged quite well, so at the moment we're modeling SDL_StorageContainer off of XNA's storage API, but of course there are other things that we want to add as well (the big one is making multiplatform asynchronous file I/O easier).
SDL_GPU
SDL_GPU is actually Ryan Gordon's project, but the FNA core team is heavily invested in this as well.
A very large component of FNA is the 3D graphics abstraction layer, which we call FNA3D. Currently we implement all of the renderers from scratch; we've always had an OpenGL renderer, but more recently we've had D3D11, Vulkan, and even Metal as supported backends! This work is by far the most difficult, and for a team of roughly 3 developers, maintaining the Vulkan backend alone is a herculean task.
Using what we've learned from building FNA3D, we're going to help Ryan finish SDL_GPU so that this work can be distributed across more developers, making the job of long-term maintenance much easier for anyone working with explicit GPU APIs. Rather than maintain a Vulkan renderer by ourselves, we'll maintain an SDL_GPU renderer and contribute to SDL as needed, like we do with basically all of FNA's other subsystems.
In particular, we intend to work on the following tasks:
- Vulkan support (already in progress!)
- PlayStation 5 support (maybe 4 as well?)
- The shader format (in particular, we plan to make an SDL_GPU shader emitter for MojoShader, which is what FNA uses for Effects support)
Upstreaming NativeAOT on Console
This is something we're collaborating with a number of different teams on, but ultimately FNA's organization is where the work is getting done right now. As previously mentioned, we would like to get this cleaned up and ready to share with the game development community at large, since it's a pretty major breakthrough and we don't want it to go to waste!
Lots of Little Bits and Stuff
Like most engineers, we have our own little tasks here and there that we want to
get done - PipeWire by default,
improving the FNA3D trace suite,
and a
Linux-native HLSL compiler and D3D runtime
are just a few of mine. You'll probably see updates on all of these as time goes
on, mostly in our #maintainers
channel on Discord.
Some Final Thoughts
With so much going on for developers right now, you're going to see a lot of technical write-ups for alternatives to Unity, which is definitely a good thing and I hope that continues even after all of this is over. But let's not forget about the organizational failures that allowed for this to happen, and let's all think very carefully about how our industry builds its infrastructure and rethink the way in which our "competition" interacts with one another. The FNA community has been thinking about these problems for a long time, and I hope that those moving away from Unity will think about them too, so that we can all build a healthier industry where something like this can never happen again.
flibitBuild
Want to replicate my build machine? Grab a RHEL8-based OS and have a look!
"RHEL-based" OSes include RHEL for Developers, Rocky Linux, and AlmaLinux.
Your OS minimum will be "glibc 2.28+, 64-bit only".
# Update base install before doing anything else
dnf update
# Add EPEL
dnf install epel-release
crb enable
# SDL2 dependencies
dnf builddep SDL2
# All the base build tools!
dnf install bzip2 bzip2-devel chrpath clang cmake freetype-devel gcc-c++ \
git gtk3-devel libstdc++-static libuuid-devel libvorbis-devel \
libxml2-devel meson mingw32-gcc-c++ mingw64-gcc-c++ ninja-build \
ocl-icd-devel openal-soft-devel opencl-headers openssl-devel patch \
perl-IPC-Cmd svn unix2dos zlib-static
# libdecor
curl -O https://gitlab.gnome.org/jadahl/libdecor/-/archive/0.1.0/libdecor-0.1.0.tar.bz2
tar xvfj libdecor-0.1.0.tar.bz2
mkdir libdecor-0.1.0/flibitBuild
cd libdecor-0.1.0/flibitBuild
meson .. --libdir=lib -Ddemo=false
ninja
su -c 'ninja install'
# PipeWire 0.3.42 (Highest version usable with meson)
curl -O https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/0.3.42/pipewire-0.3.42.tar.bz2
tar xvfj pipewire-0.3.42.tar.bz2
mkdir pipewire-0.3.42/flibitBuild
cd pipewire-0.3.42/flibitBuild
meson setup .. --libdir=lib -Dsession-managers= -Dpipewire-v4l2=disabled -Dv4l2=disabled -Draop=disabled
ninja
su -c 'ninja install'
# Also:
# Configure SDL with PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure
# The CMake config provided by SDL2-mingw is terrible, delete the block
# that starts with "if(NOT TARGET SDL2::SDL2)".
flibitBuild AArch64
This works fine on the latest Fedora. Your glibc minimum will be 2.34.
# On Fedora...
dnf install qemu-system-aarch64
dnf group install --with-optional virtualization
virt-builder fedora-35 --arch aarch64 --size 32G \
--root-password password:!!YOUR_PASS!!
virt-install --name fedora_aarch64 --arch aarch64 --ram 3072 \
--disk path=fedora-35.img,format=raw --os-variant fedora35 --import
# Inside the VM...
dnf builddep SDL2 libgdiplus
dnf install autoconf automake bison bzip2 chrpath cmake gettext-devel git \
libicu-devel libtool python tar unzip
Wait, a "plan"?
Well, this is a .plan file, so here's my TODO. Expect nothing from it, ever.
In Progress
Codename Edible Launch Prep
Awaiting Assessments
Codename a l'Orange
Codename Spinarak
Waiting Room (2024)
SDL 3.0
- SDL_ActionSet is what sponsor resources are going towards
- SDL_StorageContainer is whenever I feel like it
- Wayland/PipeWire are waiting on external components
Coding History
- On call throughout development
Anodyne FNA Console
- Waiting on a handful of external things
VVVVVV 2.4
- Giving the localization project as much time as it wants
Super Hexagon Neo Part 2
- Occasional merge work
Waiting Room (Heat Death)
ScoreRush PC
- Need AppAdmin for publish, crosshair for mouse
Proteus "Neo" Rewrite
- Retirement project