Ok, I have to admit that it wasn't entirely honest of me to post this screenshot:
...because while I told everyone I had fixed the GL_ARB_vertex_buffer_object codepath in Unreal Tournament 2004 17 years late--which was true--what I didn't say was that this screenshot was taken on a Nintendo Switch devkit.
I spent a large portion of 2020 working on a contract to port Star Wars: Republic Commando to the Switch, which was good to pass the time, because all year long the prospect of leaving the house was terrifying.
Republic Commando is a classic at this point: an Unreal Engine 2 game! Not even 2.5! This was not my first run-in with the title, either. Many years ago, I was asked to evaluate it for a Mac OS X port, and turned it down.
For me, one of the luxuries of Unreal Engine 2 is that I had spent so much of my professional life on it. Years at a desk inside Epic's offices helping craft the engine, then years porting various licensees' games. I did patches to UT2004 for about three years after it shipped, I cratered hard on a port of Unreal Tournament 3, and, if we're being honest, I've been fired at least twice by Epic Games since. Maybe three times. Wait...four? It might be four.
But, my god, Unreal Engine 2, I know it like the back of my hand. And for a good while in the mid-2000s, I would get codedrops of games and have them running on Linux within a few hours. I knew just what to touch and what to watch out for. And one thing I absolutely knew to watch out for were changes to the renderer.
UE2 came at a weird time in the gaming world; with the exception of a single special case Direct3D 8 pixel shader that an ATI engineer wrote to make terrain render faster on their hardware, it was a fixed-function pipeline game. It's still gorgeous for how primitive the tech is by modern standards. Its big innovation was the static mesh: saving models in GPU memory instead of uploading every polygon, every frame, but the rendering APIs of the platforms otherwise still wanted games that set some limited states, drew a little, and set some more states for the next draw. The industry-wide transition to the programmable pipeline was just around the corner: Doom 3 shipped in 2005, Gears of War in 2006...but until then, Unreal Engine 2 was the best of the best.
So of course Unreal Engine 2 was destined to get the same treatment that idTech 3, the prior best-of-the-best got: everyone licensed the thing designed by one of the best graphics engineers on the planet, and immediately ripped out the renderer. This is just the way of the world.
So when a copy of Republic Commando's source code came across my desk for evaluation in the mid-2000s, I did what I always did first: I looked at the Direct3D code to see what got changed. And sure enough, there was a whole shader system added to the thing. I turned the project down without further investigation. This wasn't my first time at this rodeo, and it wasn't the first time I'd been gored by this bull.
It's really hard to explain what it was like to be a professional game porter in this era. You had OpenGL, everyone else--and I mean everyone, from the game developers to the driver engineers to the players--had Direct3D. All the development resources, for features and debugging and crucial performance optimizations were simply not available to you.
It wasn't uncommon to look at a Direct3D feature a game used and just have no way to get from here to there with OpenGL. Things we take for granted in 3D graphics, like render targets, just didn't exist at all until years later. And so it also was with shaders, which had a million aborted attempts: a graveyard full of ill-conceived OpenGL extensions that no one remembers at this point. But in this timeframe, taking on a shader-filled Direct3D game and trying to make it work with OpenGL was a huge gamble. Like "I'm betting on the roulette wheel landing on zero" level gamble. More accurate: playing russian roulette with five bullets. You might succeed, but more likely that shit was gonna kill you dead.
And I, after years in the game industry and dozens of games shipped, still didn't really understand OpenGL all that well. You have no idea how long I managed to dodge this by lucking into the right projects over years and years. It's embarrassing.
So Republic Commando was regretfully forgotten, until last year when I was introduced to people that were looking for someone to do a Nintendo Switch version of the game. And I thought...here is the universe giving me a chance to right a wrong from long ago. So I said, screw it, I'm doing it this time.
The first step, as you could tell from the screenshot, was getting UT2004 running. I wasn't looking for perfection here (as you can tell from the framerate in the corner), but this codebase was a known quantity to me, so solving new problems here would be easier than solving them in a codebase where there were tons of unknowns, and I could graft those solutions into Republic Commando.
Some things I didn't solve at this point, like the extremely slow file i/o (loading a level was taking around...ten minutes or so. The Switch does not like small file reads!), which got pushed down the line to the actual game.
This got bad fast, though. One of the other developers on the project joked that "loading" had replaced xkcd's "compiling" meme:
Unreal has a file system layer abstraction, so we eventually ended up writing a Switch-specific implementation that aggressively caches game data to RAM, reading ahead in large blocks and only caching sections of files; Unreal does a lot of "I need this small section of a data file, but I'll read it over and over during level load," which was sort of unexpected. There is nothing better than fixing cripplingly-long loading times, as we were able to iterate much much faster when you didn't have time to read twitter (or like, a small novel) to test each change.
I piled a ton of open source into this game. Have to get this thing running on a new platform? Good thing we've got SDL running on the Switch already, so we can just start from the Unreal Linux code and the SDL port and when things go wrong I wrote all of this code myself and know exactly what to touch. There are movies that need to play? Drop in TheoraPlay. We've got Shader Model 1 GPU programs? That's why I wrote MojoShader.
Collectively these libraries represent years of development, and it's gratifying as hell to be able to drop them into a project and go.
Speaking of shaders, this is where my troubles began. An enormous amount of time was spent on taking this fixed-function OpenGL renderer, slotting shaders (Direct3D 8 (EIGHT) shaders!) into parts of it, and fighting with little quirks that ran differently between Direct3D and OpenGL in general. I'm not talking coordinate systems, I'm talking goofy things like glSecondaryColorPointer can't accept an alpha value (Direct3D "specular" colors, of course, can, and this game, of course, needs that, but only for a few things).
Some things were obvious fixes, and some things were extremely hard to track down. The debugging tools for older OpenGL are...not super-great. The best I found was this weird MC Escher fever dream of a hack:
Run the Windows version of the game under Wine, so the Direct3D calls get turned into OpenGL equivalents.
Record Wine with ApiTrace.
Record the game's actual OpenGL renderer under ApiTrace.
Compare the two traces to see what state is wrong.
Run ApiTrace under rr so I can tivo around in the debugger to inspect an incorrect draw call that already happened.
One time I ran the game under Wine so I could record it with a 32-bit ApiTrace, then replayed that with a 64-bit ApiTrace so I could capture that with RenderDoc. I needed an intervention.
Eventually we got everything rendering pretty, and all the rocks were the right color on Geonosis. This was months of effort, a lot of it kinda grueling. Shader programming is a lot like doing skateboard tricks. All you can really do is say "check this out," and see if something awesome happens or you just crash hard. If I had infinite time and more common sense, I would have just started from scratch and built something that used shaders exclusively, using the Direct3D renderer as a reference. The talented team that did the PlayStation 4 port, who did not have the luxury of an OpenGL implementation, did this very thing. Migrating old fixed-function stuff to the programmable pipeline is probably going to be an ongoing problem in the coming years, as marketable nostalgia intensifies, and it's not a straightforward process, which is why I balked at this approach, but it doesn't stop the regrets after the fact.
Lots of other work happened, too. I don't generally work on a team, so this was an unusual experience for me, but several other people did a lot of really great work here. They didn't just add features like rumble support while I struggled to get pretty pictures on the screen, they were hardcore about this game, and passionate about tweaking small details to perfect it. That the game renders little things at the right aspect ratio in widescreen--which the original game did not support--is one small example of this team's love of the game and desire to get it right.
Star Wars: Republic Commando is available now from the Nintendo Switch eShop, or if you prefer, for the PlayStation 4, or if you are That Guy (as I am also That Guy), available for the Switch as a limited-run physical copy (or the PS4) and even in a Switch Collector's Edition.
If you don't count Bioshock (which was kinda-sorta-not-really-but-maybe Unreal Engine 2.5 with a new renderer...as one does with UE2, remember), this is the first Unreal Engine 2 game on the Switch, and I'm sorta proud of that. The idea of "classic gaming" has moved way past Pac-Man in my lifetime, and I feel like this was a small act of history preservation, which has become a real passion of mine. I hope you all enjoy it!