hrydgard / ppsspp

A PSP emulator for Android, Windows, Mac and Linux, written in C++. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues. For discussion use the forums at forums.ppsspp.org.
https://www.ppsspp.org
Other
10.85k stars 2.13k forks source link

Silent Hill Origins - graphics investigation #16126

Open hrydgard opened 1 year ago

hrydgard commented 1 year ago

This is likely gonna be a long one, which is why I'm starting a brand new thread.

There are multiple things going wrong with the graphics of the two Silent Hill games. Going to start with Silent Hill Origins and work my way through it over time. During the investigation I will get it working on Vulkan first.

A first note is that we end up creating framebuffers of size 1024x64. This might be fine, but they shouldn't work to texture from, at least not in their entirety (max is 512), so we'll see what happens with those.

Another little side note is that if you choose any brightness setting above 0, the game adds an additional CLUT rendering pass, that grabs one color channel of the frame, applies an inverted grayscale gradient and adds that to the frame, boosting the dark colors. To make things as simple as possible I will not turn that on, plus, it seems to work correctly already.

A third side note is that due to another issue I was running with vertex cache enabled, and hit a VK validation check when the vertex cache grew. That push buffer is shared across multiple frames, but our queueing destruction of the old one should have worked to prevent any kind of problem here? I'm a bit confused.

A fourth note is that there are two main frame structures - one in the intro and burning house scene for example, which includes a bloom effect, and one for dark indoors scenes like some rooms in the hospital, where the flashlight is available. In those, no bloom is there but a super advanced shadow mapping effect is, which isn't working correctly. I guess bloom + shadows are too expensive together.

Anyway, the intro of the game, running along the road, then trying to rescue the girl in the burning house, seems to work correctly, or at least, any rendering errors are not yet visible.

We're gonna investigate some different scenes, since the game seems to switch up its rendering quite bit.

Frame structure, burning house (things look mostly correct here, but as we shall see, not completely)

The framebuffer setup is quite unorthodox. Note: In framebuffer addresses, I'll often omit the "04" prefix.

This initial walkthrough is done through RenderDoc since it's faster to see what's going on, but it's possible to miss things. Next I'll go through again using PPSSPP's GE debugger, now that I understand the structure of the frame.

d4000 seems to both be used as a double-wide 565 target, and a 8888 target, at different points in the frame - though I won't pretend I understand what's going on with that yet. Anyway, the frame starts by clearing half of it to black, as a 565. Which half seems to alternate per frame.

Then, we draw a weird little shape to a 128x128 target at 4c000. Our buffer there is much bigger for some reason.

image

Then we switch to a giant 1024x64 target, and blit the 128x128 image to the very far right side:

image

Back to d4000/00000, where we now treat it as a 480x272 8888 with Z at 00000, and render the scene to it, including the main character. This looks completely normal - well, it's pretty bad at culling so it draws a lot of out-of-viewport stuff, but doesn't matter. We end up with:

image

Next, we render the scene one more time, this time to 0170000, again 480x272 8888 with Z at 1c000. But we render almost entirely black, except smoke at the end, and at slightly more than half resolution, 256x160, probably we misdetected the render target size:

image

Back to d4000 again, where we render some fire effects:

image

At the end, we try to render the previous smoky target on top, but it has a very subtle effect:

image image

void main()
{
    mediump vec2 fixedcoord = vec2(clamp(v_texcoord.x, _25.u_texclamp.z, _25.u_texclamp.x - _25.u_texclamp.z), clamp(v_texcoord.y, _25.u_texclamp.w, _25.u_texclamp.y - _25.u_texclamp.w));
    mediump vec4 t = texture(tex, fixedcoord);
    mediump vec4 p = v_color0;
    mediump vec4 v = vec4(t.xyz, p.w);
    fragColor0 = v;
}

Next, there's a weird draw that textures from the top right pixel of 170000 and draws it to just outside the top right of 00000 treated as 8888 color this time (remember, depth was here before). No idea what that's about. We don't inject a reinterpret here because we regard 8888 and depth as incompatible for that purpose.

Moving on, we're at 170000 (the smoke). We first draw some black into the image, looks like we're clearing borders for the upcoming bloom effect stuff:

image

We now take the previous scene image from d4000 and copy it to the top left quarter, while boosting its brightness:

image

Next follows a typical bloom effect where we bounce the image around itself (which causes PPSSPP to do a lot of image copies), will skip most of the steps but here's one:

image

The bloom is then drawn on top of the image, plus some noise and a weird invisible horizontal bar:

image

And we're done.

Though there's some weirdness with the final blit! The image we end up blitting to the display is the other half of the d4000 565 image, from the last frame I guess? So there's a frame of latency? Still haven't figured out how the last frame ends up in place...

So far so good. I think we're mostly OK here actually though there's some weirdness. Next up, a scene that actually breaks. Soon!

hrydgard commented 1 year ago

Next we have a more complicated scene. This one, in the hospital, has a funny glitch where a smaller depth buffer interferes with the main one, causing this problem where the ladder from the bottom-right of the screen clips the character:

image

This glitch is a bit ephemeral though - it disappears if you save/load state, for example. That means that somewhere along the way, the combined actions we do (resizing framebufffers etc) don't come out the same way as if you just start over. You can get it to happen again by leaving the room into the elevator and coming back.

In this frame, the game does a lot of stuff early in the frame too that looks like it's rendering a depth map from the position of a flashlight. This has been previously discussed and is a separate issue that also needs solving... Let's see if we get there.

More investigation to come.

hrydgard commented 1 year ago

Alright, figured out the depth buffer problem above, see #16127 .

ghost commented 1 year ago

Next issue need to be fix is the frameskipping issue https://github.com/hrydgard/ppsspp/issues/9990

https://user-images.githubusercontent.com/37603562/192838463-07f355ed-a9c6-4f7d-88cb-91cce8182383.mp4

hrydgard commented 1 year ago

EDIT: Sorry @Gamemulatorer , next one is shadows. Though, I'm slowly losing hope on this one, my god, see below.

Okay, next up, shadows. This time I'll be using PPSSPP's GE debugger.

First, the strange shape referred to above is just the character's ground shadow. Though in this new dump, it's something else.

Silent Hill shadows ULES00869_0001.zip

After rendering some stuff (in this dump, that monster woman) in white, it goes off rendering the scene from the light's point of view into a depth buffer at 170000. It uses a clever texture projection trick and a gradient texture to render a 16-bit depth gradient using 565 mode. At the same time it's rendering an actual depth buffer to solve occlusion, but I presume this won't be used. Instead of this, they could have used the read-from-depth swizzle addresses, but I guess either they didn't know about them or it's an artifact of being a PS2 port with minimal changes.

Next, it reinterprets that buffer as an 8888 buffer, and copying over the image from the start of the frame (at 4c000) on top Nope, this is where the CLUT32 lookup on 565 was. Now it seems even more pointless since it just converts the same image to itself through some complicated steps... It does this using alpha/RGB mask ff/ffe307. Then it does it again using color mask 071fff. The result doesn't seem to change the image, just squish it horizontally by a factor 2.

Next it switches to framebuffer 4c0000 again (8888, 480x272, depth at 0), and draws the scene, plain texture, no shading.

image

Next, switch to framebuffer 170000 with depth at 1c0000, with a 256x160 viewport. There it renders the scene again, but this time no textures, but shading only.

Next, framebuffer is 170400 (!), same width (512). Depth test set to equal. Renders the scene again, to a 156x159 viewport this time.

Next (step 155), it draws stuff again, this time with a stencil test of ALWAYS, and a mask of 1. alpha mask is set to a matching fe.

Alpha blend mode is subtract (ONE, ONE). This is starting to feel like a depth texture lookup? (As the result of a subtraction can be seen as a compare...) It's using a 256x256 CLUT8 texture (CLUT is just a black->white gradient) from 0x0888cc00 (RAM) which is full of zeroes, which seems like it might not be the intended contents...

Afterwards, back to rendering at 170000 (step 168). Texturing from 170400 while rendering to 170000... Hm. We're also now in a new stencil mode, fail=REPLACE, pass/depthfail=REPLACE, pass=REPLACE, with alpha mask fe and RGB mask ffffff. Then there's a >= depth-tested draw, with 0 masks and alpha blend add.

Then finally, the scene is drawn with the light texture:

image

Now, the stencil test is pass if (81 & 81) == (a & 81) fail=KEEP, pass/depthfail=KEEP, pass=KEEP, depth test is ==.

And then a small bit of post stuff follows.

I don't understand anything, heh.

We are missing the following, according to the logs, I'll try to figure out where they occur:

hrydgard commented 1 year ago

Some more notes: When doing various operation on the shadow map, it seems to expect it to be in the right part of the 170000 framebuffer, here's some failed depth tests for example:

image

But it's not there. Think we're missing some framebuffer overlap trick, or possibly, there's some CPU processing of the image. There is that readback which I eliminated, maybe wrongly... It doesn't get recorded in the dump, of course.

Here's an interesting draw too towards the end of the last pass to 170000:

image

with quite a depth stencil setup:

image

So much to break down here...

Also, here's a video of the shadows in action on a real PSP:

https://youtu.be/cSFhK9g5mvY?t=948

hrydgard commented 1 year ago

Oh yeah, should also mention, except for minor Z-fighting-looking glitches, the software renderer works perfectly (and fast! I get 60fps!) but not with the framedump, only in-game. So the framedump doesn't capture everything we need here.

image

(shadows!)

hrydgard commented 1 year ago

Actually, almost perfectly - with the software renderer there's a glitch with funky dark colors where the characer's shadow should be:

image

hrydgard commented 1 year ago

Software-created framedump (does render correctly): Silent Hill software shadows ULES00869_0001.zip

unknownbrackets commented 1 year ago

I started up the Origins Demo, and see the funky shadow pretty clearly: Purple box shadow under feet

Let's see how this one is working, maybe it'll tell us something about the rest...

In software, we can more easily see how it double buffers using stride (unlike other games, which usually use a 512 stride and double buffer with separate buffers - this is 0x040d4000): Stride in software renderer

I think we currently detect this as an x-offset render, but in this case we could most likely use separate depth buffers. Might get tricky to properly detect, in all cases, though. Anyway, I just wanted to note this is more than just a size misdetection. Frame 2 is 480 pixels right of frame 1, and scissor/region are set to 1023.

What I see:

Shadow corrupted with depth data

This shadow is rendered indeed using reverse subtract ONE/ONE, so the white areas of the weird shape are supposed to darken the rendering, which makes sense. It uses depth testing (>=) to stay behind the person.

Looking just before the it renders the shadow at 64x64, we can see the problem along with depth in software: Depth buffer next to shadow

So these are the buffers involved: 0x04000000 - 0x04020000: depth buffer for temp buffer A 0x04020000 - 0x04040000: temp buffer B (64x64 in margin for blurring) 0x04000000 - 0x04044000: depth buffer for 8888 scene 0x0404c000 - 0x040d4000: 480x272 scene at 8888 0x0404c000 - 0x0408c000: reused temp buffer A (128x128) 0x040d4000 - 0x0415c000: 480x272 scene at 565, stride 1024 double buffered (interleaved)

So our problem is how the temp buffer B target and depth buffer overlap, for the software shadow glitch.

When it renders the projected shadow at 64x64:

Immediately after rendering that, it clears the 8888 buffer + depth:

Before the clear, depth looks like this: Shadow as depth

The shadow we care about is on the right side and gets cleared.

And texturing from the shadow uses:

So... that's weird. There's isn't space for a 1024 stride depth buffer, which seems like what it "wants" to do. There's also no CPU access to 0x04020780, at least that we see. The actual demo renders just fine on a real PSP. Let's try hacking the dump to show the 8888...

#16126_ULET00870_silent_hill_shadow_softgpu

This looks different than the PPSSPP rendering, most likely because of the depth swizzle. But it's still wrong, unlike in the game... so that means somehow the frame dump doesn't reproduce correctly?

Turns out I forgot I bought the game cheap some years ago to eventually debug it. And this happens the same in the actual game. It only shows proper shadows in hardware because it doesn't notice it's depth... and I wonder if when there are multiple things with shadows, it puts them in different parts of the margin and sometimes gets it wrong?

-[Unknown]

unknownbrackets commented 1 year ago

From the frame dump on a PSP, this is the depth: #16126_ULET00870_silent_hill_shadow_softgpu_depth

Linear version (32 pixels of margin as you'd expect): #16126_ULET00870_silent_hill_shadow_softgpu_depth

However, when I use psplink with the game running:

         - 00   02   04   06   08   0a   0c   0e   - 0123456789abcdef
---------------------------------------------------------------------
04020700 - 1552 1552 1552 1552 1552 1552 1552 1552 - R.R.R.R.R.R.R.R.
04020710 - 1552 1552 1552 1552 1552 1552 1552 1552 - R.R.R.R.R.R.R.R.
04020720 - 1593 1593 1593 1593 1593 1593 1593 1593 - ................
04020730 - 1593 1593 1593 1593 1593 1593 1593 1593 - ................
04020740 - 1400 141D 1439 1456 1473 148F 14AC 14C9 - ....9.V.s.......
04020750 - 14E5 1502 151F 153B 1552 1552 1552 1552 - ......;.R.R.R.R.
04020760 - 136C 141E 143A 1457 1474 1490 14AD 14CA - l...:.W.t.......
04020770 - 14E6 1503 1520 153C 1559 1576 1593 1593 - .... .<.Y.v.....
04020780 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
04020790 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207a0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207b0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207c0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207d0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207e0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
040207f0 - 0000 0000 0000 0000 0000 0000 0000 0000 - ................
04020800 - 121F 1220 1221 1222 1222 1223 1224 1225 - .. .!.".".#.$.%.
04020810 - 1226 1220 1217 120E 1205 11FC 11F2 11E9 - &. .............
04020820 - 1211 1212 1213 1214 1215 1215 1216 1217 - ................
04020830 - 1218 1218 120F 1206 11FC 11F3 11EA 11E1 - ................
04020840 - 1211 1212 1213 1213 1214 1215 1216 1217 - ................
04020850 - 1218 1219 121A 121B 121B 121C 121D 121E - ................
04020860 - 1203 1204 1205 1206 1206 1207 1208 1209 - ................
04020870 - 120A 120B 120C 120D 120E 120E 120F 1210 - ................

04020780 - 04020800 is zeros (128 bytes - or exactly our 64 pixels.) Moreover, if I write 1337 to 04010780... it stays there. It doesn't get cleared. What I don't properly understand is why the frame dump is not reproducing this. It seems like it's probably caused by depth swizzle, though...

-[Unknown]

unknownbrackets commented 1 year ago

Also note this: https://github.com/hrydgard/ppsspp/issues/6265#issuecomment-1236059019

I think what's happening here is clearer in the software renderer, but I suspect there's a download in between. Ugly stuff it does, offsetting into framebuffers with different formats and mixing formats.

-[Unknown]

hrydgard commented 1 year ago

And this happens the same in the actual game

Dunno if it's because I have the euro version of the game, but the shadows render correctly on my real PSP... No colored glitches. Maybe they fixed a bug?

But yeah, this game is evil in a bunch of ways.

unknownbrackets commented 1 year ago

No I mean, the behavior is the same but the shadows render correctly in game. A frame dump on a PSP renders them wrong. I haven't found what's different... I even checked the display list isn't modified or anything within the game by checking the memory, and it's the same as PPSSPP sees...

-[Unknown]

unknownbrackets commented 1 year ago

Alright, I've figured out the difference. sceGeEdramSetAddrTranslation().

The game calls sceGeEdramSetAddrTranslation(0x1000); and when that happens, the color buffer (0x0404c000) looks like this: #16126_ULET00870_silent_hill_shadow_softgpu

The shadow looks great. This is the same general problem we see in some of the hardware rendering (like that bus scene), so now we appear to have an explanation. New and exciting ways for this game to be evil.

I think it affects how the data is swizzled between mirrors. Here's a comparison: PSP rendering screenshots showing different patterns

Top is with translation, bottom is without. Left most is the 8888 buffer, middle is the depth buffer, and right side is the linear (0x04600000) version of depth. Notice the data rendering from the color buffer is in a totally different place. You can also tell in the top that the pixels "survived", just as we were observing.

Guess this'll require some reporting and a new frame dump command, etc. Gonna be a pain to accurately simulate this, but for hardware maybe we can have it affect how we detect depth buffer usage and offsets somehow.

-[Unknown]

hrydgard commented 1 year ago

Wow! I did not expect there to be even more ways to swizzle, but I guess that sceGeEdramSetAddrTranslation function had to do something... Wonder if it has even more flags.

Can't help but wonder about the motivation for this stuff.. Swizzling is logical to avoid hitting the same cachelines from both depth and color, but having multiple modes..

unknownbrackets commented 1 year ago

Alright, so here's how the mirrors work:

Mirror 1 (0x04200000) is fairly simple:

Mirror 2 (0x04400000) is identical to 0x04000000 regardless of translation.

Mirror 3 (0x04600000) is a bit trickier, but basically involves bit rotation (note: this is also how depth is written):

Definitely strange that this is even configurable. It mostly seems like there's two modes, and when it's non zero it just defines which bits rotate or etc... for some reason.

Pending to check: can you even set the color or depth buffer to target a mirror? Or are they fixed color=mirror0, depth=mirror1?

-[Unknown]

unknownbrackets commented 1 year ago

Did a bit more checking and confirmed that the bits outside 0x001FFF0 do nothing on the framebuf/depthbuf.

-[Unknown]

inukaze commented 1 year ago

Hi there, well right now i am trying to compile PPSSPP-1.14.4 (cd53526). [Under Slackware64 14.2, if not work on this, the next week i try on Slackware64 15.0]

my plan is start to play this Silent Hill EUR Version, i don't care about glitches.

i had finish things like «Limbo Of Lost» native for PC (that thing crrash on my pc every 7, 17 or 27 mins, was very ramdom crashes).

i don't had any problem with finish this title ever when the ppsspp don't broke before that.

What's thing's i should activate and log / or screen capture, to you get more accurate information for fix the most relevant things emulating this title ?

inukaze commented 1 year ago

Next we have a more complicated scene. This one, in the hospital, has a funny glitch where a smaller depth buffer interferes with the main one, causing this problem where the ladder from the bottom-right of the screen clips the character:

image

This glitch is a bit ephemeral though - it disappears if you save/load state, for example. That means that somewhere along the way, the combined actions we do (resizing framebufffers etc) don't come out the same way as if you just start over. You can get it to happen again by leaving the room into the elevator and coming back.

In this frame, the game does a lot of stuff early in the frame too that looks like it's rendering a depth map from the position of a flashlight. This has been previously discussed and is a separate issue that also needs solving... Let's see if we get there.

More investigation to come.

Dunno, exist a method for in the newer versions of PPSSPP like v1.12.3 or v1.14.4 for "Disable Stencil Test" like a "Cheat" for example. if i dont bad remember with PPSSPP v1.1.1 this help with the latern glitch

CLPSAW commented 5 months ago

Hey guys, I'm using the latest PPSSPP build 1.17 and I've heard build 1.14.4 with Dx11 had the flashlight glitch fixed. I came here to share this little information about quick-save/quick-loading which partially solves the issue temporarily, as it comes back once you enter another room or return to the room where it was temporarily fixed, so this might help you understand the problem. I tried any possible settings combination, so please bear with me, it's not necessary to ask me all of my settings, as everyone is experiencing this flashlight glitch. PLEASE find a way to fix it, I can be your guinea pig for any tests you might want me to do. Thanks a lot in advance!

Captura de Tela (4304) Captura de Tela (4305)