Ppsspp: Medal Of Honor Heroes Saga [ULES00561][ULES00988] - Bad graphics ingame.

Created on 15 Jul 2014  路  31Comments  路  Source: hrydgard/ppsspp

Title: Medal of Honor Heroes 1 & 2
Genre: FPS
Region: EU
Format: ISO & CSO
Version: 0.9.8-1611-g2da02f9
Game ID: ULES00561 & ULES00988
OS: Windows 8.1.1 64bits
Compatability: Ingame

Notes: Menus and sound are good in both games, same graphic glitches. Tested also softgpu, Non-FB and CPU/GPU, no changes.
Screenshots:
image
image

Most helpful comment

There we go. I wish I could be bothered to write those monthly update style articles like Dolphin, if so this would definitely be in it :)

All 31 comments

The cause of this is actually already known, #3607.

-[Unknown]

Any progress on this?

Edit: Probably should note on the start that this issue is no longer suspected to be caused by Unexpected prim type: 7 / PRIM_CONTINUE which is already implemented in softGpu, hence why this ancient issue was reopened to keep the two separate:].

Overall there's something buggy with TRIANGLE_STRIP prims for sure, it might even be more than one problem, as there are two cases seen in-game:

  • TRIANGLE_STRIP between RECTANGLES prims will stop RECTANGLES prim following it from showing up - that's fixed by not dirtying vertex state which is bad and probably still points to something weird with TRIANGLE_STRIP prims, removing that strip prim also shows up rectangle one that follows, seems this also work with software transform,
  • TRIANGLE_STRIP prims following other TRIANGLE_STRIP prims - this is where the primary offender happens, that is all those missing triangles on the map which everyone complains about.(Edit: also noticed walls made with TRIANGLE_FAN missing in MOHH1, but they follow TRIANGLE_STRIP prims as well, not sure if it's because FAN's are also special or just any prim can go missing if they follow strips;] which would make those two points into one).

Couldn't find anything affecting the latter, except camera movement itself it might show different triangles depending on angle, generally if less triangles show up(staring at the ground or some corner of the map) it seems to have higher chance of drawing something correctly, it also looks like there are many bad triangles under terrain that stretch to a longer distance which could be somehow related. To me it kind of looks like we have possibly unrelated strips, that somehow due to some incorrect logic existing in both software and hardware rendering get their points mixed.

I'll note that triangle strips in general are very popular. Gods Eater Burst, for example, uses them for almost all graphics.

That said, maybe there's an issue when switching prims?

What happens if you change this code in IndexGenerator.h:

    bool PrimCompatible(int prim1, int prim2) {
        if (prim1 == GE_PRIM_INVALID || prim2 == GE_PRIM_KEEP_PREVIOUS)
            return true;
        return indexedPrimitiveType[prim1] == indexedPrimitiveType[prim2];
    }

    bool PrimCompatible(int prim) const {
        if (prim_ == GE_PRIM_INVALID || prim == GE_PRIM_KEEP_PREVIOUS)
            return true;
        return indexedPrimitiveType[prim] == prim_;
    }

To:

    bool PrimCompatible(int prim1, int prim2) {
        if (prim1 == GE_PRIM_INVALID || prim2 == GE_PRIM_KEEP_PREVIOUS)
            return true;
        return false;
    }

    bool PrimCompatible(int prim) const {
        if (prim_ == GE_PRIM_INVALID || prim == GE_PRIM_KEEP_PREVIOUS)
            return true;
        return false;
    }

This will prevent it from combining prims. It should be a lot slower, but if this fixes something, it's a bug.

Are any of the vertex positions crazy for any of the prims? I think the PSP silently drops primitives with crazy coordinates, which we don't do. Crazy meaning very large negative or positive, after transform.

Oh, I guess this game has a demo.

-[Unknown]

Yeah like unknown says, strips are in nearly every game so there has to be something that's a little different here. Maybe a strip can actually continue a list, or a fan?

Tried that change just now and nothing changed as far as I can tell. Some vertices do get rather high values "shooting" through whole map, but guardband-culling branch wasn't really affecting anything visually last time I tried, through I guess it wasn't working for all games where it should, so maybe.

It's stupidly hard to skip into a prim that's drawn in visible space to tell whenever it's glitched or not and almost impossible to tell whenever the empty hole near it should will be filled by some following prims or was bugged in that place, so it's pretty time consuming and confusing at the same time:].

I also noticed UV coordinates get negative at some of those prims, it might not be related, but this reminded me of that japanese dungeon crawler discussed here which had a tiny glitch somehow showing texture when it should be showing "the other side" of it aka nothing.(edit: here's a frame dump of it just in case ULJM05167_0001.zip ) Maybe this is the other way around and just doesn't show it when it should.

Edit: I just remembered we can now record frames so here's from MOHH1:
ULUS10141_0001.zip shows black screen when playing, but debugger has all the draws and textures. It's not easy to go through them:C.

Hmm, for that dungeon game, I personally think it's plausible that culling may function even in throughmode. Not sure if related to this game though, will look.

-[Unknown]

MOHH2.zip
Here's 2 more frame dumps from MOHH2 which show something interesting, both are made on water that draws a huge hole which draws properly by just slightly moving the camera to the side.

They also show black screen when played and take time to go through debugger, so just to visualize it:
ulus10310_00009
ulus10310_00008

Culling doesn't make much sense RECTANGLE primitives at least, because drawing them "backwards" invokes the UV rotation functionality which is used very deliberately by many games. Though I suppose they could be always flipping around culling to match... hm.

Hm, I don't have a good explanation for that, though it would seem to indicate the CPU is involved ...

Agreed, but the dungeon game is drawing a triangle strip - not a rectangle - in through mode. From the recording, it seems to work fine in softgpu, which it turns out is already allowing culling in through mode.

-[Unknown]

When messing around with some debug asserts I found what I think is a fix for the RECTANGLES/TRIANGLES issue. Won't fix the majority of the broken geometry though, but committing shortly.

I'm sure checking this with Render Doc might be useless since it will not show emulation problems, but I really like how it visualize prims, might be slightly easier to imagine what's broken by looking at it, so here's an example of just flat piece of ground that has a relatively few messed up triangles:
mohhflatground
Edit: and a csv which lists vertices with coords of that shape:
1.zip

~
Interesting the list of coords starts by a few (-32.0,-32.0) which due to scale of every other being between 0.0 and 1.0 I imagine flies very far off. Soo maybe it really should be something affected by guardband-culling, it just somehow doesn't.

Oh I think that's actually nice discovery, I looked at quite a few shapes and all broken ones starts by(Edit: actually they can be anywhere on the list to break stuff) list of vertices with (-32.0,-32.0) while the correct shapes does not have them!

Edit: I can't find anything with weird negative(nor positive) constant X,Y in PPSSPP GE debugger, but seems like the same broken shapes repeat a bunch of stuff with UV (-2048.0, -2048.0) or UV (-4096.0,-4096.0) :]. I think removing vertices with those UV might fix this problem.

Things seem a little bit too broken for this to be a guardband-issue but you never know I guess. Can you check what vertex format the geometry is being drawn with? Are index buffers used, and if so is there anything suspicious in them? Seems odd that bad positions would be used as a marker or triangle strip splitter, but yeah, interesting find. -32,-32 is just two coordinates, X,Y I guess, is Z=0?

As in my edit above in PPSSPP GE debugger I can't find any repeated X,Y at all, but there are many with very fishy UV values UV (-2048.0, -2048.0) or UV (-4096.0,-4096.0), I'm convinced those are broken.

Oh I listed a pair of "broken" UV coords, but actually pre-transform they are both (0,0):].
pretransform

Im positive that discarding/ignoring those incorrect vertices might be the fix here, but this is probably not that simple, I'm confused why in GE debugger it's UV and not XY as in the RenderDoc and this also probably isn't about being 0,0, but being the 0 of whatever scale is used. I am dumb, but now I also feel dumb:f.(at least dumb people have luck and this issue required someone lucky to find something:p)

Does that mean that UV scale/offset are set?

I don't think any discard is done based on UVs. It would just be based on X/Y/Z, I think? At least, I've never seen any indication in tests of UVs causing discard, but I haven't gone hunting for it.

-[Unknown]

Here's all tabs when some potentially bad UV's going to (-2048.0,-2048.0) vertices are used together with good ones - https://gist.github.com/LunaMoo/6489173ba4453d85180f5bbd92a47c95 Maybe it is XYZ but GE debugger is inverted somehow for d3d11? That third party software was showing only weird XY's but in same places as GE debugger is showing those weird UV's... Edit: OGL shows those as UV as well... heh dunno.

Edit: Oh nah sorry, that Render Doc also shows UV... I didn't notice it says "Texcoord":P.... oops(yes I totally assumed different software keeps same order ;f)

Hmm, indeed, UV scale:

Tex U scale 64.000000
Tex V scale 64.000000
Tex U offset    -32.000000
Tex V offset    -32.000000

Hmm, the UVs are formatting a bit misleadingly. Since the texture is 64x64, a value of 65535 corresponds to "64".

Effectively, that means (-2048,-2048) - (29,12) is (-2, -2) - (0, 0). Since UV wrap is enabled, that means it's texturing from the bottom right corner of 2x2. It's a bit weird though. Seems like badly decoded vertices.

I wonder if the vertices are not (fully?) written yet, and it writes them right after enqueuing the list, or something? Maybe we're seeing verts from the last frame, with a few places randomly overwritten by other vars? It seems not bad enough for that, though...

The vert type is fairly usual but does use weights:

u16 texcoords, ABGR 8888 colors, s16 positions, u8 weights (1)

-[Unknown]

As far as I can tell all(all affected that is;p) vertices and draws are loaded when stage loads, they aren't generated while playing and from my comparison of memory dump made on PSP they seem to be the same there. So they are buggy and depend on psp behaviour.
If there are any tests I could run on PSP, I have one in hand now, but never really created any homebrew for it.;p

Well, if you want to try any tests, these are some instructions I think:
http://forums.ppsspp.org/showthread.php?tid=2822&pid=56026#pid56026

But haven't tried it on anything newer than Windows 7 - I've heard there may be problems with drivers.

I'm not sure here - if the memory dump is right, at least that seems to eliminate it being a CPU bug. Maybe we're somehow handling 1 weight incorrectly, or there's some alignment issue? Hmm.

We could also try testing discard, probably by drawing full screen triangles with different wrong verts, and resetting VRAM to 0 in between. That way we could just log which types of verts caused a discard and which drew (this is basically the same strategy as the recent mipmap test.)

But not sure, I mean, maybe we're just reading the verts wrong...

-[Unknown]

I have it up and running:3 at least I think I do, when testing gentest.py misc/libc it gave expected file, but when I tried gentest.py gpu/primitives/trianglestrip the .expected file was still empty:P, well BMP at least was showing stuff so maybe that's right. Not sure how to write that test you mentioned, but going to look and see how those tests work:].

Heh got a bit busy, but either way coming back at this I can't even figure out how to create a simple texture for a test:|.

I guess the test will need one to actually test texcoords, it might also need to test color, as all the suspicious vertices here also have 0x00000000 for color, we don't know if PSP could ignore vertices based on texcoords, color or both of those being set to 0, nor what exactly happens when triangle_strips starts from such vertices or includes them somehwere in between valid ones. Maybe other prim types also will have to be tested for it althrough at least for this issue doesn't seem necessary.

Seems highly dubious that it would completely ignore vertices with certain attribute contents. It's kind of the wrong place in the pipeline for that kind of thing. But yeah, I guess with custom hardware like the PSP GPU you never really know... and I don't have a better explanation :(

Some games rely on the GPU advancing the vertex or index pointers after a draw to provide the next vertex/index pointer. Maybe there's some edge case in that advancing that we're not taking into account, leading to slightly misaligned vertex pointers which could totally lead to issues like this...

Well when intentionally searching for an offset I noticed a pattern with those weird vertices, for example in a place like this:
VADDR: 00004c => 098e2494 BBOX_TEST: 000008 BBOX_JUMP: target= 00000048 DRAW PRIM TRIANGLE_STRIP: count= 8 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 16 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 8 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 28 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 5 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 18 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 10 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 7 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 098e2494 DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 098e2494
It starts from 8 of those "broken" vertices and suspiciously the command BBOX_TEST mentions 8.
Then we have this:
````
VADDR: 0000ec => 0951b1e4
BBOX_TEST: 000008
BBOX_JUMP: target= 000000e8
DRAW PRIM TRIANGLE_STRIP: count= 13 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 13 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 14 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 5 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 12 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 7 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 19 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 5 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 13 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 5 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 8 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 10 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 7 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 8 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 10 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951b1e4
DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951b1e4

````

which also repeats that pattern, 8 broken vertices and BBOX_TEST: 000008 before it. Not sure if that's the case everywhere, but maybe that is something. Also it seems that memory which stores vertices in those places has more than when it finishes last one, so maybe that does require an offset like you say.:]

Oh wow! That must be it, look at this:
VADDR: 000050 => 0951ee78 BBOX_TEST: 000008 BBOX_JUMP: target= 0000004c DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 9 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 5 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 4 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 6 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 7 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78 DRAW PRIM TRIANGLE_STRIP: count= 3 vaddr= 0951ee78

Now first draw:
X Y Z U V Color NX NY NZ 202.352051 119.616333 65153.773438 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 202.186035 87.817017 65151.984375 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 149.712524 119.565063 65146.078125 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 149.306763 87.152954 65144.218750 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000
second draw
X Y Z U V Color NX NY NZ 202.398438 119.690430 65164.917969 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 202.237183 88.778442 65163.226563 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 151.249023 119.642090 65157.648438 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000
and third draw:
X Y Z U V Color NX NY NZ 150.861450 88.151245 65155.890625 -4096.000000 -4096.000000 00000000 0.000000 0.000000 1.000000 151.510986 110.656250 65157.210938 92.750000 87.500000 3c3429ff 0.000000 0.000000 1.000000 151.128906 109.875122 65157.101563 92.250000 85.250000 3a3228ff 0.000000 0.000000 1.000000 151.896729 109.886597 65155.632813 96.000000 85.250000 3b3329ff 0.000000 0.000000 1.000000 151.680054 109.247070 65157.144531 93.000000 83.250000 3c3329ff 0.000000 0.000000 1.000000 151.896729 109.886597 65155.632813 96.000000 85.250000 3b3329ff 0.000000 0.000000 1.000000 151.680054 109.247070 65157.144531 93.000000 83.250000 3c3329ff 0.000000 0.000000 1.000000 153.573120 107.995361 65157.355469 96.000000 78.750000 3f362cff 0.000000 0.000000 1.000000 200.988525 107.776001 65164.089844 58.250000 79.500000 42392dff 0.000000 0.000000 1.000000

Exactly 8 right after that command in different prims, so as you say, it must be an offset thing.

Edit: Hah made a stupid implementation of adding 0x90 to gstate_c.vertexAddr in GE_CMD_BOUNDINGBOX when VADDR was used before and it fixed the problem in first instance of the problem which hopefully confirms it:) ~ my change also broke further draws, but whatever I just wanted to test the theory the offset probably needs to vary instead of being 0x90 like I hardcoded by looking where vertices are stored. Another edit: I think it needs to be 2x BBOX_JUMP: target

AH! Yeah! BBOX needs to also advance the vertex pointer. How could we have missed this...

Well it was easy to miss considering it broke only 2 games, or maybe more, who knows, maybe it was somehow affecting other ancient mysteries, maybe even that infamous Tekken leg shaking^^.

Highly doubt that it has anything to do with the leg shaking...

I'll whip up a fix later today, anyway.

There we go. I wish I could be bothered to write those monthly update style articles like Dolphin, if so this would definitely be in it :)

Very nice catch @LunaMoo and @hrydgard, who knows this might even fix little uncommon glitches elsewhere too. I think that happened with bezier.

-[Unknown]

Was this page helpful?
0 / 5 - 0 ratings

Related issues

radiocaravan picture radiocaravan  路  6Comments

fuentescg picture fuentescg  路  4Comments

fahadfoyjur picture fahadfoyjur  路  5Comments

Deivmsr picture Deivmsr  路  6Comments

ultrasuper19 picture ultrasuper19  路  4Comments