Pixel Processing Problems: On the Road to Pixel Perfection

The old name was better

The GameCube GPU is a complex, tight-knit piece of hardware with impressive features for its time. It is so powerful and so flexible, it was used unmodified within the Wii architecture. For a comparison, just imagine a SNES running with an NES's graphics system. This is completely unheard of, before or since. The GameCube is a remarkable achievement of hardware engineering! With its impressive capabilities, emulating the GameCube's GPU has been one of the most challenging tasks Dolphin has ever faced.

As well as games work, developing proper emulation of the GC/Wii GPU continues to be an ongoing and difficult process. Dolphin has had long standing issues with a bunch of its features, and many graphics glitches have been around for years and years. With the merger of the tev_fixes_new branch (tev = Texture EnVironment, a major component of the GameCube's pixel processing pipeline), Dolphin's emulation of the GameCube GPU is going to the next level.

History Lesson: The Flipper/Hollywood GPUs

The two consoles' Graphics Processing Units - called Flipper on the GameCube and Hollywood on the Wii - have a lot in common with their modern PC counterparts, but there are just enough differences to make perfect emulation an absolute nightmare. One quirk in particular is the use of integer math within the console GPU pipeline - as opposed to floating point math which is the norm for PC GPUs.

For a quick retrospect: When Dolphin was being developed behind closed source walls in 2003, only a small portion of the GameCube's hardware features were even understood. The only choice for many years was to guess and check until the emulator produced the right results - trial and error. Despite being an uphill battle, the Dolphin developers hacked and clawed at issue after issue, making exceptions, hacks, changes, and adjustments trying to force the two systems to come up with the same answers. But the GPU's pixel processing was particularly problematic for them, leading to lots of minor defects. They knew the reason for these errors - Dolphin was doing floating point math for Flipper's integer calculations - but PC GPUs of the time had no support for integer math.

As neat as Midna looks here, those are not her true colors.
But for a long time, unknown reasons prevented it from rendering correctly as shown here.
Super Mario Sunshine's level textures show some funky colors.
The effect is meant to look far more natural.

Despite the trial and error development and the confusingly complicated code, Dolphin's floating point approximations of Flipper's integer math are actually very good. At a time when few games were playable and there were much bigger problems, there was no need to do anything better. And even if there was, the hardware of the time made it impossible anyway.

As the years passed, issue after issue was tackled and solved within Dolphin, but the GPU pixel processing bugs remained, and they became more and more obvious as time went by. Even in the Dolphin 4.0 release, some of the oldest and most prevalent issues in the emulator stem from the emulator's use of floating point math within the GPU pipeline.

The sleeping armos has odd defects on their textures.
All of Wind Waker's graphical issues are related to pixel processing.
The Zelda games have some of the best examples of problems with Dolphin's GPU pipeline.
They made a great testbed for potential changes and fixes over the years because of this.

The Problems

Since Flipper uses integers for its graphical calculations, the Dolphin code needed to use some fancy tricks to emulate integers with floating point math. Take for instance an (unsigned) 8 bit integer: the maximum value that such an integer can hold is 255, i.e. if you add one to 255 it would overflow to 0. How do we emulate this with floating points? Believe it or not, it needs to be as complicated as:

frac(value * (255.0/256.0)) * (256.0/255.0)

And that's not actually good enough - integers do not have a fractional part, but any values computed as floating points do. This makes comparison operations between intermediate values very iffy. Imagine we calculate two floating point values, say one is 49.999 and the other one is 50.003. On the GC/Wii GPU these values would be equal since they are rounded to the closest integer - whereas with floating point math we would say they are not equal. This is a huge problem since it can make the difference between an object showing up on the screen or just not showing up at all.

The Dolphin code up till now uses loads of complicated and confusing code to make scenarios like this work - yet it fails various places. First, did you understand what the code snippet above is supposed to do? If not, then you see the problem: it's absolutely not obvious how all this floating point magic ends up emulating integers properly. Actually it was just plain wrong and really needs to be even more complicated to work correctly, namely value - 2.0 * round(0.5 * value * (255.0/256.0)) * (256.0/255.0).

A feature that highlights this annoyance is the Legend of Zelda: Wind Waker's water highlights. Depending on the video card (AMD, NVIDIA or Intel Integrated) and the video backend (D3D9, D3D11 and OpenGL), you will see pink water highlights at night. Fixing it in one place meant breaking it in another setup or environment.

This underflow and overflow varies per hardware, making it difficult to fix.
Using integers, there is no ambiguity and the correct color is determined regardless of the running environment.

Despite all kinds of workarounds, and all kinds of complicated rounding formulas, it never really worked properly. While it is fairly easy to find an issue on your computer that happens in one backend and fix it, that very often (if not always) broke things on another configuration. There are tons of phantom bugs caused by differences in GPU and CPU rounding that Dolphin couldn't handle no matter how much it tried.

Unfortunately, it doesn't get any better with other features of the Wii/GC GPU, such as alpha testing, depth comparison and many others. All of the situations that involved floating point conversions and their hacks made for hard to read code that was difficult to maintain.

Bad floating point estimations can cause awkward layering problems like this.
Integer depth and alpha testing has no room for error.
Raindrops hitting the track in Dolphin 4.0 are not masked properly.
The effect is far more subtle when properly emulated.
Even something as simple as a map can be a nightmare to render.
Properly ordered 2D elements look much better.

With all of these conversion issues, even a game that may look like it's working properly on the surface can have tons of minor problems that go unnoticed. Everyone knew why this was happening, and with issue after issue vanishing from Dolphin, it was becoming more apparent that this needed to be dealt it. It was time to fix this problem.

Solution 1: Overhauling Floating Point Conversions

Since integers seemed impractical to use directly at that time, an attempt was made to improve Dolphin's mimicry of integers in floating point - primarily through more aggressive usage of tricks like the one mentioned above. Whenever some variable was supposed to be an integer by the hardware behavior, code was added to make extra-sure Dolphin emulates this correctly. Unfortunately, this had some serious problems.

The additional math instructions per pixel heavily degraded emulation speed. And even with the massive CPU hit, the games still did not work as well as the software renderer! No matter how precisely integers were faked, many of the issues known to be caused by floating point errors couldn't be fixed regardless.

There was also a price to pay for the developers. Simply put, the code was a beast to maintain: it became ridiculously complex, and the correct strategy to emulate integers was never really clear. In fact, it later turned out that the approach was doomed to fail to begin with, as 32 bit floating point numbers (somewhat ironically) are just not precise enough to emulate 24 bit integers.

Even with better floating point support, the bubbles still show up improperly.
Software renderer proved it could be fixed, so there was a huge pull for the SMS bubbles to work after the overhaul.

The first attempt to fix pixel processing was slow, complicated, and impossible to maintain. More than anything, it became apparent that there was no easy fix for this problem. Mimicry didn't work, Dolphin needed to just use integers, like the GameCube and Wii. Unfortunately, integer support meant that there were going to be sacrifices. On top of some older hardware being unsupported... D3D9 had to go.

D3D9 and all GPUs designed for it are unable to perform integer calculations. Integer support was added in D3D10 and OpenGL3, so the D3D11 and OpenGL backends support it easily. But because of the nature of Dolphin's common GPU emulation abstraction layer (videocommon), as long as the D3D9 backend remained in Dolphin the floating point code had to remain. With advancements in D3D11 and OpenGL4.4, the D3D11 and OpenGL backends were technically superior than D3D9, and already capable of superior GameCube/Wii emulation with very high performance. After the release of Dolphin 4.0, the decision was made to remove the D3D9 backend to clear the way for future developments - like integer support.

The time had finally come for the biggest graphics overhaul in Dolphin history!

Solution 2: Implementing Integers in Dolphin Emulator

The second approach, used by the tev_fixes_new branch, was to actually use integers rather than tricks to approximate them. Complicated code like

zCoord = zCoord * (16777215.0/16777216.0);
zCoord = frac(zCoord);
zCoord = zCoord * (16777216.0/16777215.0);
immediately simplified to

zCoord = zCoord & 0xFFFFFF;

This was unthinkable in the past, but with advancements in hardware, drivers, APIs, and Dolphin itself, this is finally a plausible option. The tev_fixes_new branch debuted early this summer to develop this integer support, and the results have been astonishing.

Since 2009, the Software Renderer has used integers and proved that a lot of these issues could be fixed!
...This issue wasn't one of them. Yet, with these changes to the hardware backends, they work. Dolphin - making sense as usual!

The benefits were immediately noticeable in both the code and the games. Problems that had plagued the emulator in hundreds of games from day one simply disappeared. And since the integer calculations are performed by the GPU, there are no CPU performance regressions!

The merger of tev_fixes_New is one of the biggest improvements in the history of Dolphin development. By addressing the Wii/GC GPU quirks head on rather than using hacks and workarounds, it can fix and change problems that no one has even reported yet. We honestly don't know how many games this affects! A large part of the Wii/GC graphics pipeline is included within these changes.

Compatibility has significantly improved due to these changes. Virtual Console NES games were once garbled messes of lines, but now show graphics and become playable. They aren't perfect yet, but the issues lie elsewhere within the emulator.

Going forward, switching to integers will make the code easier to read and adjust for current and future developers alike, allowing Dolphin to continue to improve long after the merge.

Without the image next to this one, you'd have no idea what game this was.
The Legend of Zelda: Collector's Edition's NES titles are now fully playable on Dolphin thanks to these changes.
Another NES game that can barely be identified.
Castlevania III (VC) isn't perfect yet, but it's much improved and definitely playable.

What Does It Mean

With the merger of tev_fixes_new in 4.0-1192, Dolphin now uses integers for pixel processing. Accuracy has taken a big leap forward, but how does this affect users? Depends on their video card.

  • AMD Radeon - Radeons are really good at integer math, to the point that some games appear to run faster than before! There may be some isolated performance regressions out there that haven't been caught yet, but so far it's been nothing but good for Radeons.

  • NVIDIA - Nvidia is known to have weak integer performance, so everyone assumed beforehand that changing to integers would seriously impact Nvidia users. Surprisingly, Nvidia isn't as bad with integers as we thought. D3D performance on Nvidia appears to be almost completely unaffected. There is a small overall speed decrease of around 5%, and there are a few weird regressions such as Four Sword Adventures, but D3D handles integers very well.

    Unfortunately Nvidia's OpenGL doesn't work as well. NVIDIA users, save for the GTX 780 and Titan, will see significant losses in OpenGL performance for the time being. The performance cost increases the higher the internal resolution is raised, so users with stronger Nvidia cards should be able to run OpenGL with only some lost enhancements, and in the worst case scenario 1xIR should work on even the lowest machines.

    We have a decent idea what's going on, and it may be possible to optimize around it or for Nvidia to better optimize their OpenGL drivers. Even without the optimizations, the latest Nvidia GPU architecture used in the Titan and 780 have proven quite capable with integers, so hopefully Nvidia GPU's integer performance will improve with new hardware. Until the situation improves, Nvidia users should stick with D3D if at all possible.

  • Intel - There wasn't as much testing done on Intel integrated graphics cards as we would have liked. We do know that an Intel HD3000 can still run Dolphin fine after the merge, but performance comparisons were not done.

  • Qualcomm - Qualcomm is completely broken by this merge. Not that it ever worked very well.

  • Mali - Mali still works fine, but no performance testing has been done.

By using integers, hundreds of bugs are being solved in a single change! Dolphin's code is cleaner than ever before, making development easier and more appealing to new talent - the kind of talent needed to keep Dolphin development going for years into the future. The only downside is some performance regressions in some scenarios, but new hardware and driver releases will solve them soon enough. Integers allows Dolphin to continue moving ever closer to its goal of greater accuracy, for now and in the future to come.

However, there's still lots of work ahead of us: While tev_fixes_new fixed up the pixel processing part of the GPU pipeline, we already know that similar issues are plaguing the vertex processing pipeline. Stay tuned!

Known Issues Fixed

Issue 540 - Super Mario Sunshine Bubble Effects
Issue 3313 - The Wind Waker Pink Water Highlights
Issue 3426 - Four Swords Adventures Zbuffer Issues
Issue 5058 - Skyward Sword Map Rendering Issues
Issue 5283 - Twilight Princess Missing Flames
Issue 5325 - Twilight Princess Minimap Inaccuracies
Issue 5414 - Midna's Fused Shadow Severe Texture Defects
Issue 5501 - Harvest Moon Magical Melody Map Missing
Issue 5682 - Skyward Sword D3D11 Crash
Issue 6041 - F-Zero GX Raindrop Splash Drawing
Issue 6197 - Mario Party 8 Swervin' Skies Corruption
Issue 6391 - Super Smash Bros. Melee Background Issues
Issue 6442 - Super Mario Sunshine Stage Texture Issues
Issue 6683 - Wind Waker Armos Issues
Issue 6816 - Mario Party 4 - 7 Textbox Issues
Issue 6841 - Skyward Sword Time Shift Issues
Issue 6932 - Gozilla Destroy All Monsters Melee Water Issues

And countless others that will never be reported.

Die Diskussion kann im Forenthread des Artikels fortgeführt werden.

Nächster Beitrag

Vorheriger Beitrag

Ähnliche Beiträge

  • Keine ähnlichen Einträge.