Fixing graphics bugs
Added 2020-04-09 23:25:21 +0000 UTC
Today I decided to try fixing a bug that has been present for quite some time in Mega Man 11. But this time, I also decided to share the process that I use to find and fix those issues.
First, let's start with the tools. If you have ever attempted to repair something, you probably already know that doing so without proper tools is almost impossible. It's no different with graphics bugs. To find what is causing the bug, we need something that allows us to capture the entire process that the GPU goes through to render the frame, and analyze it step by step until we can isolate the problem.
There are currently 3 main graphics debugging tools that are still actively maintained, and I will talk briefly about each one.
NVIDIA Nsight: This is a tool from NVIDIA as the name suggests. I suspect that it only works with NVIDIA graphics cards (I never tried with anything else). It is pretty good, the main problem that I have with it is that it seems to be very unstable when working with Ryujinx. Furthermore, it does not allow saving frame captures to disk, so if it does crash (something that happens quite often on my end), it's gone, you need to start over.
Apitrace: This one is a bit different. Instead of capturing a single frame, it captures all of them. It records every single call that the application does to the graphics API functions, allowing them to be "replayed" later. This has some advantages, and some disadvantages. The main disadvantage is that you need to wait until it "replays" everything and gets to the specific draw call you want to analyze.
RenderDoc: This is also a great tool. The only problem with it is that it does have a few annoying bugs, especially when working with OpenGL. But it also seems to be very stable, so this is the tool that I use most of the time, and it's also the tool that I will be using today.
So, now that we know the tools we have at our disposal, let's start the debugging session.
The first step is running the game on the emulator, getting to the area of the game where the bug can be seen and doing a frame capture.

Here we are. As you can see, Mega Man eyes are black. No, he is not wearing sunglasses, and this is not how it is supposed to look like at all.
If you pay attention to the above screenshot, you can also notice other issues, but right now, the one we'll be trying to fix is the black eyes.

After opening the capture, we can see a window like the one on the image above. The list on the left side contains all draw calls. Each one of those draw calls is responsible for rendering something on the scene. For example, one draws the robot dog, others the boxes, another the floor, others the bushes, and there is also one responsible for drawing Mega Man. There are no set rules for what is drawn first.

By selecting each draw call, we can see each object being rendered, one by one. The output tab you can see on the right side shows the output buffers. In this case we have 2, one color buffer, and one depth buffer.
The color buffer is the one selected, and it contains, as the name implies, color information. This is what you see on the screen. At this point the render is not yet complete and does not represent the final scene.

On the image above we can see the second buffer, the depth buffer. This one contains depth information. It tells the GPU how close each pixel is to the camera. The black regions are closer to the camera, while the white regions are more distant.
The depth buffer is used to do something called depth test. Depth test compares the depth value of the current pixel, with the value stored at the same position on the depth buffer. Thanks to the depth buffer and depth tests, the order that the objects on the scene are rendered doesn't matter. The depth test is used by the GPU to decide if the pixel should be rendered or not. If the pixel depth is lower than the one on the depth buffer (closer to the camera), it is rendered, otherwise, the pixel is behind the previous pixel, in this case it is discarded.
One interesting thing that we can already notice so far, is that the eyes are rendered correctly on the above images. So something must be breaking it at a later point. So let's continue looking...


Here we can see the exact point where it breaks. Compare the two images, and pay attention to the list on the left side. They are showing two different draw calls, and the second one (the one with value 205) is where it breaks.
Now that we know where it breaks, we can proceed and analyze more information about the call. Usually, one of the first things I look at is the shaders. Incorrect shaders can cause all sorts of issues, so the first thing I try to find are missing instructions or fishy code on the shader. In this case, I did not see anything unusual. So I decided to look at the last stage, where the output of the pixel shader is supposed to be written to the buffers (color and depth buffers, as we have seen before).

This looks mostly correct, but there is one odd thing... Blend is not enabled. Blend can be used to combine the output color of the pixel shader with the color that is already on the color buffer. This can be used to create transparency effects for example. Since the black eyes are being rendered on top of the regular eyes of the character, I would expect it to use blend here to make those black eyes more transparent. This is mostly what the debugging work consists of. Trying to figure out what the game is trying to do, and why it is not working.
If we follow this line of thinking, then we can assume that we have a low value for the alpha output of the pixel shader. There is an easy way to check that, we can just enable the alpha channel on the texture viewer.

Bingo! The black eyes are not black. They are fully transparent. But something is preventing it from being blended with the pre-existing color on the color buffer, so it ends being rendered as black.
So there are two possible issues here: Either blend is supposed to be enabled, but it isn't for some reason, or it is supposed to do something else to make the black eye transparent. Thinking about the things that the game could use here, one thing came to mind. Alpha test. Alpha tests are used to discard pixels depending on the value of the alpha channel. It can discard the pixel if the value is too low (like 0 for example). Alpha test is not currently implemented on Ryujinx, so this could be very well what is causing the whole problem.
Before actually attempting to implement the alpha test, I decided to first check if the game was using alpha tests at all. Because if it doesn't, then implementing alpha tests will not change a thing on this game.
To check if alpha tests are used, first I need to know the specific GPU register that holds this information. We can get this information from open-source NVIDIA GPU drivers. One of them is Nouveau for Linux. There is also the recently released homebrew API Deko3D, where we can also extract this information from. A third option is extracting it from the proprietary NVIDIA driver through reverse engineering. I plan to show how I do that at some point, but not today.
After adding some logging on the emulator to notify us when the alpha test is used, I found that this game does indeed use it! So the next step was fully implementing it and doing another test. Will it fix the bug?

Yes! Not only it fixes the black eyes, but also many other issues on the scene. It is now pretty much perfect.
I hope you have enjoyed this brief explanation of the process I use to find the cause of graphics bugs and fix them. I plan to do more posts like this one in the future, with more complicated bugs, and also covering other things. Please let me know what you think of it in the comments section below, which will allow me to improve in the future.
That's it for now, stay tuned for more posts like this in the future!
Comments
Hello, I'm a new project collaborator and I would like to know where I can download this Dirty version of the emulator. Thank you very much in advance.
Luciano Jose de Santana Santos
2020-04-27 19:06:58 +0000 UTCFor me the game often crashes, when saving automatically. what can I do that does not happen
Tobias S 89 .
2020-04-25 22:58:22 +0000 UTCGreat post! I'm trying to learn how to improve the emulator and this stuff makes me life much easier :) Thanks and great work!
2020-04-10 02:01:30 +0000 UTCThat....looks SUPER time consuming. i commend you for having the drive to find all these bugs and fixing them!
ChickenNuggets
2020-04-09 23:43:19 +0000 UTC