Pcsx2: Jak and Daxter Precursor Legacy - Broken shadows and eyes

Created on 20 May 2014  路  43Comments  路  Source: PCSX2/pcsx2

The Eye's on the characters are not rendering correctly as well as the Shadows
tested both GL/DX Hardware backends

jak and daxter

Bug Hardware Texture Cache GS

Most helpful comment

Well I understand the various issues of the game. Extra tests won't help.
The major issue (eye and such) is related to the texture cache. The game renders multiple texture into a single frame buffer. It creates a mega textures. Then the game read back the frame buffer as input texture. Unfortunately the texture cache is based on the start address and not the position inside the frame buffer
Here an example

  • draw left eye at FB=0 position (0,0)
  • draw right eye at FB=0 position (64, 0)
  • Then render the left eye on the character with texture=0

    • the texture cache will detect that address 0 is a previous frame buffer. It will convert it and will use it. Good

  • Then render right eye on the character with texture=0x20

    • the texture cache won't detect that address 0x20 is a previous frame buffer. It will render garbage. (The TC ought to detect that it is actually the frame buffer 0 with an offset)

As a side note, most of the remaining issue are related to this texture cache limitation. It isn't easy to fix. It will badly impact the perf for 2 reasons

  • it will require to search harder in the texture cache (need to compute the area of all frame/depth buffers)
  • it will wrongly invalidate more textures so less cache hit. So more texture conversion and more pcie transfer...

All 43 comments

Daxter eyes look creepy.

Looking at the software rendering (which works), the eye textures most likely are updated every frame. Is this a texture coherency issue???

Crash of the Titans as the same issue as Jak and Daxter with the weird shadows
crash

JAK II as the same issues with the eyes, face is messed up too.. plus those weird shadows
jak ii

Jak III , those shadows and seems to have mipmapping isses similar to Ratchet & Clank
Ratchet & Clank and Jak & Daxter series uses the same engine.
jak iii

Has any work on this been made, any fixes?

This is not an open forum for questions of all kind but a developer discussion. Please ask such questions in our forum. Probably Gregory again got a mail because something with gsdx label got a new comment...

Mipmapping is a complex issue in gsdx hardware rendering and is not prioritized in current development. I would expect a solution at some day in the future as Gregory already said that it might be solvable by extending the current code.

I know the issue with shadow. The game uses a special accumulation method on the alpha channel, it need careful rounding and RW RT.

See #168

Christmas Day 2015 - I usually play this on my PS3 but I love 4K on my PC. It seems more like you should implement textures being saved to the VRAM of most ppl. Dolphin had a similar approach and it (for the most part) worked. Converting Read-only textures to VRAM and allowing the emulator load only from the VRAM.....just a thought.

oh vram can be used !!!
RW == read AND write texture

I'm not sure if you are being sarcastic or actually considering the thought

I was checking up on a related bug report on the forum and found this issue. These issues are still active in the latest git releases. And the eye bug is a texture cache issue.

See #168 for the full details.
On the SW renderer, you can use UserHacks_AutoFlush = 1 to render shadow properly (cost 5 fps).

HW renderer will work but will be slow as an ass.

I need the CRC/version/name of game that suffer of the shadow issue. I have an alternate solution.

Here's the CRC for Jak and Daxter the Precursor Legacy [NTSC-U] -- 472E7699

@gregory38

SW renderer works correctly with the new hack, but HW isn't rendered correctly here

gsdx_20160908195451

@MrCK1 Hardware is correct. Like tri-aces game, the render target will be rescaled which will cause interpolation glitches. It isn't fixable if we need to support custom resolution. Anyway, I added a CRC hack to use an alternate draw call implementation. If you want to help me collect CRC of Nobbs66, issue #168 , your version and open a PR :+1:

@LasagnaPie
could you provide me a gs dump of shadow issue of Crash of the Titans (and CRC too, so I can test it on my side).

I pushed a new version with various CRC. Feel free to test the openGL renderer + accurate blending on basic.

Is this fixed now with the Auto Flush hack? (I think that's the right one)

Is this fixed now with the Auto Flush hack? (I think that's the right one)

No and no, that's not the one. Both issues are not fully resolved.

ok np :)

Shadows are fully resolved for me. On the SW side you have auto flush, on the HW, you have an alternate shader (potentially we could do it differently to reduce speed impact but not sure).

It remains the texture cache issue on the eyes (and likely other texture too). I would need to work more on the TextureInsideRt hack.

Shadows often bleed(spikes stretch to the ground like the old bugged shadows used to) around the edges in HW, especially in cutscenes. I don't consider that fully resolved.

Give me a gs dump. It is likely a rounding error not sure it can work better on the gpu.

@gregory38

Would manual QA in an effort to isolate the issue help at all? Basically I could just load up/test a bunch of settings. If you have any particular test cases in mind i could create a bunch of snapshots and see how they well they fare.

Just a fellow QA person who's a big fan of the series seeking to help in any way I can.

Well I understand the various issues of the game. Extra tests won't help.
The major issue (eye and such) is related to the texture cache. The game renders multiple texture into a single frame buffer. It creates a mega textures. Then the game read back the frame buffer as input texture. Unfortunately the texture cache is based on the start address and not the position inside the frame buffer
Here an example

  • draw left eye at FB=0 position (0,0)
  • draw right eye at FB=0 position (64, 0)
  • Then render the left eye on the character with texture=0

    • the texture cache will detect that address 0 is a previous frame buffer. It will convert it and will use it. Good

  • Then render right eye on the character with texture=0x20

    • the texture cache won't detect that address 0x20 is a previous frame buffer. It will render garbage. (The TC ought to detect that it is actually the frame buffer 0 with an offset)

As a side note, most of the remaining issue are related to this texture cache limitation. It isn't easy to fix. It will badly impact the perf for 2 reasons

  • it will require to search harder in the texture cache (need to compute the area of all frame/depth buffers)
  • it will wrongly invalidate more textures so less cache hit. So more texture conversion and more pcie transfer...

So right now the texture cache is basically a hash map with O(1) lookup time, right? Because for area lookup there exist nice data structures as well with lookup in O(log n) ([link]), so performance point 1 can be worked around. Point 2 I don't understand, why would more textures get invalidated wrongly?

No the texture cache is an array of chained list for input texture (1 list by GS memory page) + 1 list for depth buffer + 1 list for frame buffer.

The devil is on the detail. GS memory format is awful and not linear. Texture neither frame buffer have real size. Maybe it could work maybe not. However I'm sure it will be tricker.

For point 2, if you don't support the area, it is possible to have multiple textures that do overlap. With current cache, if I'm correct, you will have 2 textures. With a cache that handle overlap, you might invalidate the 2nd (or 1st) texture (more correct but slower).

@gregory38 So, apparently using (UserHacks_TextureInsideRt=1) fixes the eye issue according to this thread Thread Was that the point of the hack?

The hack was a tentative to solve the eye issue. It doesn't fully work, it misses some invalidations (could be slow). And I limited the hack to single page texture, it must be extended to 2 (easy).

it will require to search harder in the texture cache (need to compute the area of all frame/depth buffers)

it will wrongly invalidate more textures so less cache hit. So more texture conversion and more pcie transfer...

But if you break down a texture in multiple textures the invalidation only interest smaller amount of data an potentially requires less work in cycling.
The real problem would be to recognize the area: potentially any write with a different frame buffer offset is to be treated as a new texture, with area equal to the amount of data written.
But this IMO could lead to less useless uploads of textures, and also would help in recognizing such fine grained usage of the FB as this one.

Sorry I don't understand what you mean. Let's say you have a texture that is 8 GS pages. And a framebuffer of 32 GS pages. You want to split the single texture into 8 textures. So you need to upload 8 textures. And you need special handling of texture filtering at the border.

What I'm trying to say is that in the eyes case having two separate textures in the cache would help having an hit for the left eye and then a hit for the right eye.

Citing again your post explaining on the left right eye:
It creates a mega textures. Then the game read back the frame buffer as input texture.
I'm wondering if we can avoid to reproduce this behaviour and keep the mega-texture split instead of merged into one texture.

On modern GPU, you can only render into a single frame buffer. If you want to emulate the GS behavior, you need to bypass completely your GPU HW (aka do a SW renderer on GPU). GS is a raw memory. There is no border around texure of whatever.

However, it makes me remember something. On texture cache, we maintain a reference of texture by GS page (so a 4 GS pages texture will be stored in 4 list). However frame buffer is handled differently, only first page is used. I.e. a 4 GS pages fb will be stored in a single list. My guess is that it is related to the mutable format of the frame buffer. For example, the size depends on the rendering. So it is more complex to handle.

Another idea would be to maintain the last writer of a GS page.

  • GS color rendering (data is on GPU)
  • GS depth rendering (data is on GPU)
  • EE texture upload (data is on CPU)

Otherwise fully emulate the GS memory in a shared (CPU/GPU) memory but upscaling won't be supported well for some postprocessing effect.

The process of reading back from frame buffer for later rendering is the one which builds a Source texture out of a Target texture in LookupSource, right?

I mean you have one Target texture (the mega texture drawn in the frame buffer) from which you may build up different Source textures (at different offsets). In this way you can hit on different Source textures later. Let's say a Source for left eye and one for right eye.

By the way the code you are referring to when talking about lists is this:

void GSTextureCache::SourceMap::Add(Source* s, const GIFRegTEX0& TEX0, GSOffset* off)
{
    m_surfaces.insert(s);

    if(s->m_target)
    {
        // TODO

        // GH: I don't know why but it seems we only consider the first page for a render target
        size_t page = TEX0.TBP0 >> 5;

        s->m_erase_it[page] = m_map[page].InsertFront(s);

        return;
    }

//...

The current state of the code doesn't let you hit on let's say the second page of a frame buffer (Source with m_target true) even if it covers more than one page (as you just remembered) and it contains valid data.

Btw, don't we have ANY hint on which is the frame buffer Target to read with when doing LookupSource? Like: we know that there is a Target (valid, not invalidated, in the list!) starting at address 0x00 that spans up to 0x40 (left and right eyes), we need to read from 0x20 to 0x40 (right eye) so we have here (in the Target with base address 0x00) the data we need, and build the Source out of that data.

Oh by the way GH is me ;) It was an attempt to document a bit the texture cache behavior.

Look at this code/hack. We can detect some stuffs already. However we need to invalidate the newly created source after a new rendering. Once invalidation is done we can improve the lookup to manage bigger texture properly.

Yes I know that was you, infact I think most of the comment in the code are yours 馃憤

Invalidate correctly a Source after a new rendering would mean to fix this, if I understand correctly:

                else
                {
                    // render target used as input texture
                    // TODO

                    if(b)
                    {
                        m_src.RemoveAt(s);
                    }
                }

So we need to:

  • Improve detection of Target when looking for mid frame buffer (likely mega texture behaviour) in LookupSource
  • Detect the pages covered by the newly created (mid or not) frame buffer Source with m_target and add the Source to all the lists.
  • On invalidation invalidate all the pages of the Source that are covered by texture and interested by the write.

I don't get why the procedure (that considers all the pages) that is currently used to invalidate non m_target textures wouldn't work also for m_target textures.

Sorry, I forget to reply.

Improve detection of Target when looking for mid frame buffer (likely mega texture behaviour) in LookupSource

Yes. You can look at m_texture_inside_rt in GSTextureCache.cpp. It is basically the part that search a texture inside a target.

Detect the pages covered by the newly created (mid or not) frame buffer Source with m_target and add the Source to all the lists.

If I understand your question, this part is generic. Once we have a hit, a source texture is build and saved in all the cache structure. So next access won't miss

On invalidation invalidate all the pages of the Source that are covered by texture and interested by the write.

Yes. There are 2 kinds of invalidation. You need to invalidate the texture if the EE write a new texture into GS memory. Or if GS render into the texture. Here it is likely the latter. And you need a generic version of

uint32 bbp = bp + bw * 0x10;
if (bw >= 16 && bbp < 16384) {
    // Detect half of the render target (fix snow engine game)
    // Target Page (8KB) have always a width of 64 pixels
    // Half of the Target is TBW/2 pages * 8KB / (1 block * 256B) = 0x10

    const list<Source*>& m = m_src.m_map[bbp >> 5];

    for(list<Source*>::const_iterator i = m.begin(); i != m.end(); )
    {
        list<Source*>::const_iterator j = i++;

        Source* s = *j;

        if(GSUtil::HasSharedBits(bbp, psm, s->m_TEX0.TBP0, s->m_TEX0.PSM))
            m_src.RemoveAt(s);
    }
}

The above code is for snowblind engine games. Those games cut the frame buffer in half and extract 2 textures from it. It is the same behavior for Jak except it isn't half frame buffer but random part of it.
So basically the first if must be replaced with a bpp loop that range from bp to end_block. With off (memory info) and rect (size of the rendering), you should be able to compute the end_block.

I don't get why the procedure (that considers all the pages) that is currently used to invalidate non m_target textures wouldn't work also for m_target textures.

There is a lots of crazyness in the GS emulation. Even me, I don't know all details. But current behavior allow to virtualize a bit the GS memory. I mean you can have overlapping frame buffer without bothering with invalidation. The real size of GS framebuffer is 2048x2048 which is basically all the GS memory. So you can render anywhere from any color/depth buffer.... Most game will do a normal rendering, but for example I saw some game uses an huge draw to clear the full memory.

Don't know if any of you guys know, but the eyes can be (temporarily) fixed by swapping between HW and SW mode by pressing F9.

@SpiffyJUNIOR even if you can get this to work and I could only get it to work once, it just replaces the eye with a frozen texture. Same as UserHacks_TextureInsideRt

Anyway aside from that when you pick OpenGL, shadows also look correct but it slows the game down to a crawl for me

Closing in favor of #168

Was this page helpful?
0 / 5 - 0 ratings