The Mistake
There comes a time where everyone must admit they made a mistake. This is one of those times...
In the last update, I'd concluded that the now-infamous Xbox push buffers did not play a significant role in Cxbx's failure to play Burnout 3. This was based on a handful of observations, the first being that the 3D geometry appeared to primarily be rendered using a standard D3D function called "DrawIndexedVertices". While the docs suggest that any function can be recorded into a push buffer (by running them between calls to BeginPushBuffer and EndPushBuffer), this didn't seem to be what the game was doing.
The second was that I had nopped all the push buffer code - as far as I could tell - tested it on original hardware, and the 3D still seemed to render fine. This felt like conclusive evidence that the push buffers must not be used for anything important, if anything at all.
Current appearance of the garage/car selector in Cxbx
At this point, thinking I'd have to come up with a new lead, I took a small break from the project for a few months. When I came back, I decided I should investigate more about how Cxbx draws vertices, and consult a friend much more knowledgeable about these things to help. But before I did that, I thought I'd better do my "push buffer nop" test again so I can more precisely document my findings, and make sure my evidence was as conclusive as I thought.
What I discovered was that it was actually anything but...
What happened?
Since I took that two-month-long break, I can't remember exactly what I did or what function(s) I nopped, but I can take a guess at what happened. Xbox executables are statically linked, which means all of the Xbox APIs (including Direct3D, DirectSound, DirectInput, etc.) that the game uses are actually baked into the game itself. That means when searching for D3D8 related stuff, like m_Pusher, you have to ignore the D3D8 library code that's been statically linked in, because it's not really relevant to investigating the game's own usage of it.
However, I think I misidentified a lot of functions as not belonging to the game when they actually did, and I was probably too quick to dismiss whatever I did nop as having "no effect". While you could argue that if I couldn't see a difference, it wasn't important enough to worry about, it's probably better to know as much of what's happening as possible.
Okay, so what's really happening?
While my final conclusion was wrong, my other findings do still seem to be correct. There are indeed push buffers being used in the game code, but there's a lot of evidence to suggest the game isn't
trying to use them; they're just here due to the XDK's aggressive code inlining. Since Xbox executables are statically linked, this opens the door wide open to inlining and link-time code generation (LTCG). Both of these significantly reduce the overhead of an application calling into other functions by embedding (aka "inlining") the called functions into the caller. While this is definitely beneficial from a performance standpoint, it makes the kind of automated patching/injection that Cxbx tries to do an absolute nightmare. Xbox stuff that ought to get replaced (e.g. push buffers) becomes ingrained into the game code, generally requiring manual intervention (essentially what we're doing) to resolve.
When you think about it, it's definitely plausible that they wouldn't explicitly use push buffers. I've heard B3's primary development platform was the PS2, since that was the harder one to get 60 FPS out of, and as such it's unlikely (though not impossible) that the game contains a lot of Xbox-specific optimizations.
When I redid my nop test, I found at least 8 functions that use push buffer code that are definitely part of the game itself. For reference (mine as much as yours), the functions are at the following addresses:
Code: Select all
0x32020
0x367c0
0x3ba10
0x3bfb0
0x3da90
0x3e520
0x3fee0
0x44170
So I went through again nopping them one by one to see the effects each one had on the game. The results were actually very interesting...
Society if there were no push buffers
For the most part, what I've found is that push buffers are used for a lot of post-processing effects like bloom and motion blur. Check out that lighting artifact that appeared in the top left corner in the video above.
At first I wasn't sure where it came from, but then realized it's lighting from the garage, which calls the same function too (I didn't nop it there):
Presumably if I'd left the garage at a different time, the artifact would be completely different (or perhaps not visible at all if there was no bloom in the scene). This is why I say I was likely too quick to dismiss my original tests as having no effect; some of these are so subtle it would be easy to assume nothing had changed.
After nopping a bunch of the functions and noting what they did, I eventually stumbled upon this one:
For anyone familiar with how Burnout 3 plays in Cxbx, this will look immediately familiar. Yep, I think we've found our culprit! While it may not be the
only thing breaking 3D, it's definitely causing the most immediate problems, and is therefore our prime suspect.
I haven't yet analyzed this function enough to know exactly what it's doing yet, but as you might expect it contains a ton of inlined push buffer code. This is where my theory that these are actually inlined D3D functions rather than "handwritten" push buffer calls becomes important. If the game was intentionally using these, I'd have to figure out what it's doing and rewrite new code by hand to accomplish the same goals. However since I'm pretty sure they're just D3D functions, all I should actually have to do is identify which one is which, and swap them out with their non-inlined equivalent. At that point, Cxbx/Windows should be able to handle that stuff itself.
That's certainly the next thing on the agenda, however while testing, I realized something I didn't expect. Disabling this function may have broken the 3D in-game, it did
not break the 3D in the garage...
Dude, where's my car? Oh, it's right there.
If you don't know, the garage in Burnout 3 is where you select the car you're going to drive whatever particular race you chose with. It appears in the menus as some 2D images over a 3D background where the camera circles around a car in a parking garage environment. As you can see in the screenshot at the very top of the article, the 3D is completely broken in Cxbx. However, despite now replicating with Cxbx did in-game, the garage was
still working on hardware.
...Huh, so something else is breaking the garage?
While I was testing each push buffer function, there was one I couldn't quite nop on hardware. For some reason, the game would just freeze when I tried. I assumed I just nopped it wrong (it can be hard to keep track of the stack when doing static analysis) so at first I didn't think much of it, but I later realized it was freezing right when the garage was about to appear.
Interesting, so it sounds like this function is definitely relevant...
Only three of the aforementioned functions are used in the garage: one that handles that lighting/bloom effect, one whose changes were too subtle for me to notice (maybe motion blur?), and this third one that had frozen as soon as I tried entering the garage on hardware. I decided to zero in on that third one and see what I could get out of it.
I was still assuming I just nopped it wrong. Since I could use a real debugger with Cxbx, I decided to nop the function (and all associated stack instructions) there instead. Once I had something I knew worked, I could port that patch to the executable and run it on hardware to see what it actually did.
Instead, something completely different happened...
Suddenly all the 3D appeared! The cars, the garage, the spinning camera, it's all here!
Well, by "all here", I mean the textures are janky as hell, all those push buffer-based effects definitely aren't working, and it only works in the garage; in-game still looks the same as before. But the fact that it's actually
rendering something is more than I've ever seen the game do on PC!
(Note: the cars themselves and their reflections actually seem pretty spot on. at least from a glance. It's only the environment textures specifically that seem to be corrupted. I realize the globe texture in the menus is also corrupted, I wonder if that's the same texture issue as the garage environment...)
Okay, so this is actually really interesting. I still don't even know if I just nopped it wrong on hardware or it happens to freeze for some other reason; once again, I haven't investigated the function closely enough to know exactly what it's doing, but my curiosity is absolutely piqued now. I'll have to try it properly.
Once again, this means there are definitely push buffers being used in critical parts of the pipeline here, but to my surprise, removing them (at least this one in the garage) actually
makes it work better.
Obviously, my plan is to eventually port even the subtle push buffer code faithfully to PC (I'm not going to just rip them out and say it's good enough), but just seeing this stuff
kinda sorta work answers and clarifies so many of my questions. For one thing, the vertices themselves indeed do not require push buffers as I suspected; I mean if it did, it'd be impossible for something to be rendering right now without much more significant rewrites. That call to DrawIndexedVertices was not a red herring after all. It's quite likely in-game works very similarly, just with a few more push buffer things in the pipeline getting in the way. If I can get those to work (or at least untangle them to see if I can get something displaying at all), the game should be theoretically fully playable, if a little graphically garbled.
...Or at least that's my guess.
Regardless, this feels like a ton of really good progress. Not just in the sense of having something draw where it didn't before, but also in how much this has confirmed or disproven. I feel like before I was largely stabbing in the dark, but now I know exactly what code I need to investigate next. I can't wait until the 3D works ingame. Even if the environment textures are still garbled, it will still be Burnout 3 running more or less natively on PC.
We'll see how far I get by the next update!