February came and went quietly, especially on the blog. While we do prefer to run a Progress Report every month, we were put into a bit of a bind. February saw a lot of interesting changes - but most of them were setting up for changes that weren't quite ready to be merged yet. Rather than rushing things or writing a Progress Report about things that would be coming soon, we decided to wait.
Well this Progress Report is no April Fools' joke - a lot of big changes landed! While a lot of what's important is under the hood, some of what has been finished has major user facing implications as well. If you were sad about missing a Progress Report for February, have no fear, this double report should more than make up for the lengthy delay!
Because so many of the changes rely on one another, we're going to be jumping around quite a bit between the two months. So hold on tight, and get ready. This is a big one.
5.0-6259 - Correct Infinity Behavior of A/C Fog Coefficients and 5.0-6335 - Implement Table Based Fog by stenzek¶
Before we get to any of the big stuff, a few changes hit in early February that may have gone unnoticed. These two bugs and the changes that fixed them are unrelated, so we don't have the foggiest of ideas why these two entries got mashed together.
5.0-6259 fixes some fog bugs that have existed the better part of forever. In three titles (though no one knew these issues were related...) certain scenes would become blanketed in impenetrable fog. The most commonly reported of the titles was TMNT and My Word Coach. Prince of Persia: Warrior Within also ran into the same bug, but it manifested differently and was only reported earlier this year. What's the commonality between all of these games? Ubisoft.
Among Dolphin's developers, Ubisoft titles have become a bit notorious for having strange behaviors that rely on unintended/unknown hardware features. The Smurfs: Dance Party and Red Steel are both Ubisoft games with unique behaviors that we only recently fixed through thorough hardware testing and more in-depth emulation. So as soon as we saw these three Ubisoft games doing something weird, we knew we needed to test the games on console. Stenzek started poking at fog through hardware tests to figure out what could be going on.
So what did the tests reveal? Normally, when a game asks the GPU for fog, it sends 0 for the minimum fog range (as close as possible to the screen), 1 as the maximum fog range (as far as possible from the screen), and inbetween values for anything in the middle. However, these Ubisoft games are using NaNs (not a numbers) to represent the maximum and minimum fog ranges instead. The ArtX GPUs of the GameCube and Wii interpret NaNs into positive infinity or negative infinity, giving the result the game wants. Dolphin was forwarding the NaNs to the host GPU unaltered, but OpenGL and Vulkan don't support NaNs and flushed them to 0 - the closest fog range. D3D handles NaNs correctly, rather than interpreting them to ±infinity, leading to an equation that always results in the nearest fog range as well.
Even after he discovered what was going on, Stenzek still had the task of trying to find a logical way to introduce this case to Dolphin's fog formula. Unfortunately, he couldn't figure out a way and decided to special case it. So now, instead of giving incorrect fog, Dolphin will test for this particular case and intercept NaNs so that Dolphin can give the expected output to the host GPU. With this in place, the afflicted titles finally render correctly.
While he was doing hardware tests for one fog issue, Stenzek got roped into fixing up another minor issue in the area. Fortune Street has had quite a few interesting graphical issues in Dolphin over the years that have perplexed developers. One particular issue that was solved in the software renderer but not the hardware renderers was the table based fog mode the game used for the Memory Match mini-game.
In this case, he didn't even need to do hardware tests! All he had to do was port over the software renderer implementation to the hardware backends. With this, all of Fortune Street's known graphical issues are finally fixed.
A few months ago, Dolphin Android finally got support for Dolphin's GameINIs. This allowed Dolphin on Android to automatically load up various settings needed to run games properly.
The result of this change was overwhelmingly negative, at least among our most vocal Android users. This is because some of the settings applied to various games are quite demanding, and by avoiding applying those settings, you could make the game run a lot faster at a glance. Unfortunately, not applying some of these settings is quite damaging to the experience. In Super Mario Sunshine, the game can't tell what goop has been cleared without Store EFB Copies to Texture and RAM enabled. If you turn it off, it'll look like you're clearing the goop but it won't actually be cleared, masking that anything is wrong. On the other hand, in a game like The Legend of Zelda: The Wind Waker, EFB Access to CPU is enabled and only required for the pictograph and lens flare effects. In Sunshine's case, the feature is absolutely required for an enjoyable experience, but in Wind Waker's case, the extra performance of disabling the feature could make it more enjoyable despite a few emulation bugs.
These INI files are extremely important and improved Android's accuracy for hundreds of games, but Dolphin's mistake was forcing on these INI features without giving them an in-app way to modify them. There was no real way to override them at all without manually editing the INI files with a text editor.
In order to rectify this situation, gwicks added a game properties page to the gamelist much like the desktop UIs. But unlike them, you can force almost any setting on a per-game basis right in the UI. This not only allows users to override preset INI settings, but also advanced users can create their own custom INI settings with any of the features available in the UI.
As a warning to users who complained about a drop in performance from INI support - Dolphin doesn't apply it's more demanding options just for kicks. Even in the case of Wind Waker, you can potentially crash the game when using the pictograph with EFB Access to CPU disabled. By changing the default settings used for a game, you may cause gamebreaking glitches that could ruin your experience.
No matter how good a game looks, if the controls are unusable, it's going to be tough to play. While most controllers can do a decent enough job to replicate the GameCube experience, touchscreens leave a lot to be desired. Add in the fact that Dolphin's touchscreen controls lack some of the refinement of professionally developed games, and playing action games designed for controllers gets to be near impossible.
The worst problem when playing any kind of GameCube/Wii platformer on Dolphin's touch controls is trying getting the joystick to a neutral position. Imagine you're standing at the edge ready to line up a jump and you tap the joystick... but you're slightly off center and run straight off the edge! Because there isn't a tactile input, it was extremely hard to accurately feel how you're moving.
gwicks has remedied this by making it so when you touch a "joystick" region, it starts out centered regardless of where you touch. That way, the player knows whenever they touch the region they don't need to worry about immediately moving and can adjust from there. This makes Wind Waker, Super Mario Sunshine and other games that need precise movement much easier to play.
Dolphin, as a complex emulation software, needs to convey a lot of information to users as intuitively and unobtrusively as possible. The current speed of emulation, the game that is running, the graphics and audio backends that are running, the users system specs as dolphin detects them (useful for diagnosing errors), and warning messages are all things we need to present. The OSD (On-Screen Display) is one of our most important communication tools. It allows us to display warnings and information that is important enough to take real estate on the screen, but not important enough to interrupt play and throw a pop up in your face. It also handles the on screen FPS counter.
However, the OSD is quite old, and while its fixed size pixel font was totally fine on 72dpi and 96dpi CRTs of the day, these days it can be a little small. On a modern 4k computer monitor (~170dpi) it's pretty difficult to read, but on extreme hidpi displays like the Galaxy S9 (570dpi) or S8+ (529dpi), it is nearly impossible to see!
degasus noticed this problem and has instituted a quick fix of sorts. If Dolphin is rendering in a space 2000 pixels wide or more, the OSD text size is doubled. That is a very very simple way to fix it, and so far only applies to OpenGL, but it's quite effective!
This is a temporary fix, though. We plan to move to a proper font and scaling solution for the OSD that will easily scale across all displays and DPIs. But for now, this simple fix will help hidpi display and phone users see OSD messages.
So if we have plans for a proper solution in the future, why did we make this simple fix now? For a new OSD message. As our Android userbase has grown, so have increasing complaints about performance being utterly horrible, with FPS in the teens on brand new devices. This is generally due to the same issue that plagues macOS - the lack of Buffer Storage, an extension critical to Dolphin's OpenGL performance. So we've put a notice into the OSD that will inform users why their performance is so poor.
If you see this message, there is nothing we can do to improve your device's performance, it just doesn't support critical features that Dolphin needs. Please ask your device manufacturer for updated drivers with Buffer Storage enabled.
The Great VideoCommon Rewrite¶
One of Stenzek's main projects the past few months has been rewriting and cleaning up Dolphin's graphics core: VideoCommon. The main benefit to this is that the backends will become more unified and share more code. Right now, we have three implementations of EFB Access, but, once everything is complete with Stenzek's rewrite, we'll only have one common implementation that all three video backends call into. This is easier to maintain and allows optimizations to be crafted for all video backends at once.
While some of these changes were merged in February, a lot of the benefits hadn't started landing until the middle of March. Rather than listing the individual commits, we're going to go into the major features that were merged.
Probably the most exciting of the bunch is the unified Shader Pipeline Cache. For those of your curious as to what's changed, let's rundown the major changes that are userfacing throughout this change.
- TEV configurations are now stored in UID files for all backends
- All Graphics Backends can share these UID files
- Dolphin no longer invalidates the Shader Cache every revision
- Drivers without support for a Shader Cache (Apple) can take advantage of the ShaderUID Cache
The last time we talked in depth about shader generation was with the release of Ubershaders. Ubershaders are a solution to completely stopping shader generation stuttering given strong enough hardware, but there were still tons of improvements we could make to the shader generation code to improve the user experience for everyone. The biggest change is that all TEV configurations Dolphin encounters are now stored in the new ShaderUID cache. This intermediate cache between specialized shaders (unique to your hardware) and the emulated console can be shared between graphics backends. A lot of users would stick to one particular backend because if they moved to a new backend, that meant they'd had to encounter all of those shaders all over again. While Ubershaders more or less solved this on powerful hardware, some users didn't have strong enough computers or wanted to use internal resolutions that were too high for Ubershaders to run full speed, so, making specialized shaders work better was still a priority for the team.
With the unified ShaderUID Cache it doesn't matter which backend you use, once you encounter a particular TEV configuration, Dolphin will be able to generate a specialized shader upfront. Even if you upgrade your graphics card or change from Intel to NVIDIA to AMD, Dolphin will still have everything it needs to generate the specialized shaders for every TEV configuration you've encountered. Users wanting to upgrade builds all the time need not fear either - Dolphin does not invalidate the unified shaderUID cache between builds!
There is one downside to this - the UID cache needs to be compiled into specialized shaders that match your hardware. But Stenzek has already turned that problem into a thing of the past. Dolphin will silently compile the ShaderUID Cache into specialized shaders in the background while the game is running. While on most computers this process is completely silent, some hardware configurations have trouble with it. If you're having issues, feel free to enable "Compile Shaders Before Starting" in the graphics settings. This will make Dolphin compile the ShaderUID Cache before the game boots up.
Presenting a Change to Shader Generation¶
The above changes come with even more changes! If you look for the old Ubershader settings menu in the Graphics Settings->Enhancements tab, you'll notice that they've vanished. But, you need not worry, Ubershaders has just moved to the general tab.
Rather than using buzzwords such as Hybrid Ubershaders the new options actually describe how Dolphin is handling shader generation behind the scenes to help users get the experience they desire.
- Synchronous - Previously Ubershaders disabled, and the only option that existed before Ubershaders. TEV configurations are converted to Specialized Shaders to run on the GPU, and if a shader hasn't compiled in time, emulation pauses (stuttering) while it waits for it to finish.
- Synchronous (Ubershaders) - Previously Ubershaders Exclusive. Uses only Ubershaders to render.
- Asynchronous (Ubershaders) - Previously Hybrid Ubershaders. Uses Specialized Shaders primarily, but, instead of waiting for shaders to generate, Dolphin will fallback to Ubershaders to avoid stutter, and then switch back once the Specialized Shaders are compiled.
- Asynchronous (Skip Drawing) - New option! See below.
Asynchronous (Skip Drawing) allows users to sacrifice rendering accuracy to prevent shader compilation from causing stutters. If a shader has not finished compiling for an object, Skip Drawing will simply ignore rendering that object while emulation continues. That object will usually pop into existence the frame after compilation is completed.
While visual errors are never ideal, there are a lot of systems among our userbase with performance and driver issues that struggle with Ubershaders. Though it has trade offs, this option allows users on those systems to avoid shader compilation stuttering too.
Users that have been following the project for a while may note that this is similar to "Asynchronous Shader Generation", a feature that Dolphin developers have said would never become a part of Dolphin Master. A few things have changed over the years that opened the door for this to happen. Primarily, stenzek's hard work on rewriting Dolphin's VideoCommon core made implementing this feature painless. Asynchronous (Skip Drawing) is implemented in less than thirty lines of code for the core feature thanks to being able to reuse the asynchronous fallback from Ubershaders, making it extremely clean and noninvasive.
Plus, we have Ubershaders now, the ultimate solution to the shader compilation stuttering issue. Developers were worried that if we implemented "Asynchronous Shader Compilation" first, no one would bother to create the proper fix and we'd be stuck with the accuracy-sacrificing solution forever. Now that we have Ubershaders, we're ok with adding Skip Drawing for those that need it.
VideoCommon all the things!¶
One of the big fears of adding a bunch of features is adding a bunch of complexity. Every feature comes with a maintenance cost, so, sometimes adding a much wanted feature for users can come with costs that burden the project for years to come. Yet, despite all of these new features, Dolphin's codebase has gotten a lot cleaner, and that's because everything is being moved to VideoCommon.
Instead of each backend having to implement features, they now use VideoCommon. Originally when writing Ubershaders, phire had to write an implementation for each backend. It was one of the chief arguments for removing the D3D12 backend - the maintenance cost of it was too high and preventing features we wanted and needed.
With VideoCommon now housing shader compilation (with more to come), there is now just one implementation of each feature that the backends can call into. This makes implementing optimizations easier while making it less likely that we break one backend when adding new features. It also means we're far less likely to run into a D3D12 situation where a graphics backend becomes a burden. In fact, it would be easier than ever to write a new backend for Dolphin!
While the move isn't quite complete, we're already starting to reap the benefits of moving more and more features to VideoCommon.
Development Diary - The Shovelware that Buried Dolphin¶
On a dark and dreary February night, an issue report was made that would send several developers on an arduous journey full of danger and dismay. It all sounded so harmless, so simple.
"Hey, Casper's Scare School: Spooky Sports Day doesn't work!" a user reported. Like the vulture he is, JMC47 quickly descended upon the report. He put the user through the usual paces, demanding an MD5 hash, any odd settings, and anything else. "Most of the time, the issue is with the user," he confidently chortled to himself.
Yet as more and more information came, a solution he did not see. Hence, he descended upon an online store and ordered the game for himself. Again and again he has ordered games hoping for an interesting issue only to find that the game just needs eXternal FrameBuffer emulation enabled or something else boring. A week later, the game arrived, he dumped it and booted it up in Dolphin.
"The disc could not be read" proudly proclaimed the game in a bright yellow text. JMC47 smirked, thinking to himself, "You dare challenge me?" He started going through Dolphin's options one by one, throwing everything from the most accurate settings to the most ridiculous, hoping to find something that would break open this issue and reveal the path forward. From Speed-Up-Disc-Transfer-Rate to CPU Clock Override to less known features like region overrides and GPU Clock hacks, he went through everything over the course of three days.
Every time, the game replied, "The disc could not be read," without fail.
While JMC47 was more than confident with his ability to test games, once he exhausted all of his options he would often go to someone more familiar with coding. He analyzed what the game was doing and sought out what assistance from Leoetlino. He explained, "The game reads a testfile and fails. Surely it is an IOS issue." Considering that Leoetlino was extremely familiar with Dolphin's IOS code, he naively agreed to look into it. After all, on the Wii, interfacing with the disc drive is handled by IOS. JMC47 continued to do what he could. He used his Shuriken USB Gecko clone to dive into the game as it ran on console, dumping the RAM so it could be compared against Dolphin.
Over the next few nights, the two of them toyed with the game with Leoetlino's loader. He looked at it a much more analytical way than the tester, and tried to figure out how it was failing rather than why. Through a mixture of hacking both the game and Dolphin, he was able to make it boot. "I think it's a timing issue," he suggested. JMC47 wondered, "How can we find out for sure?"
So they dived in, night after night, two developers halfway across the world, all for Casper's Scare School. Using Gecko.net, they stepped through the instructions and slowly began to piece together what was happening. Leoetlino made sense of the situation by statically analyzing the executable. He dove through the assembly and came back with an answer that even JMC47 could understand, "It's reading the testfile to memory, and then verifying that the value at the end of the read buffer is not
12345678. If it fails that check, then it says the disc cannot be read."
"Why is Dolphin failing that?" asked JMC47 curiously. Leoetlino gave his hypothesis, "The read must be completing and overwriting the invalid value before the game checks on console. It is probably a timing issue."
So he tried to modify Dolphin to make the game boot. He made hardware tests to verify IPC timings and changed them to actually be realistic to the range of consoles they had to test. Unfortunately, the game still did not boot. No matter what he did, he couldn't get the game to boot in Dolphin without making reads complete instantly — which was obviously wrong. So he changed his attention to console. Using his custom loader as a base, he wanted to make it so the game would no longer boot on console. He sent JMC47 version after version of his loader, making the timings more and more unrealistic.
"Surely the game won't boot with this one," he claimed with another revision of his loader. Yet again and again, the main menu loaded. Frustrated, they started making more aggressive patches. With these patches came a risk. "I'm going to patch the Drive Interface module, this could brick your Wii," warned Leoetlino.
"I'm okay with that," confidently answered JMC47, "That's why I have backups." But in his heart of hearts, there was a shred of fear. Despite the Wii having spotty Wi-Fi, a loud disc drive, a broken controller flap, and a dying NAND, he couldn't help but be fond of it. It was the Wii he'd received on his birthday in 2006, one that he had fought many issues side by side with. Also he was lazy and never had charged batteries, so the fact it could boot directly into the Homebrew Channel was incredibly handy. Losing it would be tragic, but, he had trust in the resilient white beast sitting near him on his desk. But at the same time, he knew that the Wii would want nothing else but to face the danger in place of its non-boot1 vulnerable brothers. For if they were bricked, there would be no saving them.
Leoetlino was very careful with the aggressive patches and made sure to at least test what he could in Dolphin. But, because Dolphin doesn't emulate the Starlet, there were quite a few bumps trying to patch IOS. At first, the game froze before booting. They added diagnostics and discovered the patches weren't even applying. An endeavor that began early February inched toward the beginning of March.
Not even hours after the turn of the month, Leoetlino finally got the patches to function on console. "I've delayed the reads by eight seconds," he explained, "If it's a timing issue, this will break it on console." With all of their patches sorted out, JMC47 loaded the game once more heavily anticipating the result. It sat on a black screen for upwards of thirty seconds. What he saw made his eyes go wide.
With dismay, the dutiful tester reported back that it still booted. "Then let's delay it 32 seconds!"
The longer delay didn't do anything but make the game take longer to boot up. It did not flinch. With their endeavor now taking up weeks and countless hours, they were both back to square one with nowhere to look. That's when JMC47's ultimate talent crept into the equation. His luck.
While Leoetlino was searching for new breakpoints to continue analysis, JMC47 played around with Gecko.net, slowly stepping through the game's boot process on console instruction by instruction. While he did this, he watched the read buffer at
8076BBD8 closely. When he popped back over to the memory view, he noticed something incredible.
"The file is slowly getting zero'd out!" he immediately reported. He quickly took a screenshot of Gecko.net to show Leoetlino his discovery.
After looking at where the game was, Leoetlino sadly announced the situation, "You're currently in the dcache invalidate function."
In an instant, the mystery was abruptly solved. The game wrote invalid values to the read buffer, but the dcache was invalidated by the DVD read function later on. The reason why Dolphin failed was that it was writing directly to memory. On a real console, because of the data cache being invalidated, the invalid values were never actually stored and so the game would never see
While they were thrilled to have solved the issue, there was a bitterness to the discovery. Emulating the L1/L2 cache simply wasn't an option. Not only would it make Dolphin between three and seven times slower, it also would take a massive effort that'd very likely outlast any single developer's effort. That left them with the option of implementing a per-game hack, or simply leaving it broken.
Considering that they had done all the legwork to solve the issue, after days of thinking it over, they agreed that adding a patch to the game's INI was harmless and would allow the game to work in Dolphin.
With the two weary of Casper, a developer with scars past from battles with dcache emerged from the shadows to see their handiwork. After barely surviving his battle with the final two pillars of the Disney Trio of Destruction booto felt the need to give the two of them a bit of closure on why the game would do this.
"What they're doing isn't exactly an unheard of pattern. They're just initializing the buffer to something invalid, triggering an asynchronous action to fill it, and then checking if the invalid thing is still there." His analysis was concise and put to rest any fears that this was a repeat of anti-emulation code as was in the Disney titles. "In the ''Trigger Asynchronous Action' part, the game is secretly undoing the initialize buffer bit. Subtle problem, but, it went undetected on console and thus wasn't fixed. Simple programming error."
And so a tiny mistake by Red Wagon Games prevented Dolphin from booting the game. The patch added to the INI has no ill-effects to the game and silently fixes the issue. And while it may seem pointless to work so hard and spend so much time just getting a shovelware title to work in an emulator, not even a week later, Leoetlino found himself on the forums navigating a user to the Patches tab so that they could play Casper's Scare School: Spooky Sports Day in Dolphin.