xemu-project / xemu

Original Xbox Emulator for Windows, macOS, and Linux (Active Development)
https://xemu.app
Other
2.83k stars 282 forks source link

Spy-vs-Spy: Main menu geometry is corrupt #1072

Closed abaire closed 2 years ago

abaire commented 2 years ago

Title

https://xemu.app/titles/5454007c/#Spy-vs-Spy

Bug Description

Note: This is an intentional dup of the generic #309 and #537 so I have a place to put my game-specific analysis.

The main menu in Spy Vs Spy renders with highly distorted geometry, most of the draws are clipped entirely due to the fact that the w component of the programmable vertex shader is very negative.

E.g., one of the screen space vertices: {320.7315979004, 253.5145263672, 49013.19921875, -100978.4765625}

Screenshot_20220617_070622

Expected Behavior

The menu should not be corrupted, and draws should generally be unclipped and projected properly.

xemu Version

Happens in 0.7.39 and has apparently been happening for a long time (or forever), at least since 0.6.2

System Information

CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz OS Platform: Linux OS Version: Ubuntu 21.10 Manufacturer: NVIDIA Corporation GPU Model: NVIDIA GeForce GTX 1070/PCIe/SSE2 Driver: 4.0.0 NVIDIA 470.129.06 Shader: 4.00 NVIDIA via Cg compiler

Additional Context

The game uses S32K mode for the vertex positions, which is not something I've come across before. I wrote a quick test to validate xemu's behavior and it seems to align with what I see in HW, so there's something else going on here.

abaire commented 2 years ago

Vertex shader:

MUL oT0.x, v0.wxyz, c[120].zx + MUL R0.yzw, v0.wxyz, c[120].zx
MOV R0.x, c[125].z
DP4 R11.z, R0.yzwx, c[98]
DP4 R11.x, R0.yzwx, c[96]
DP4 R11.y, R0.yzwx, c[97]
ADD R10.xyzw, v2, c[129]
DP4 oPos.w, R0.yzwx, c[99]
MUL R10.xyzw, c[121], R10 + MOV oPos.xyz, R11
MUL oT0.y, v1.w, c[120].z + RCC R1.x, R12.w
MUL oPos.xyz, R12.xyz, c[58].xyz
MAD oFog.xyzw, R11.z, c[108].x, c[108].y
MUL oD0.w, R10.w, c[125].x
MOV oD0.xyz, R10
MAD oPos.xyz, R12.xyz, R1.x, c[59].xyz

Filtered for the bits that contribute to oPos:

MUL R0.yzw, v0.wxyz, c[120].zx
MOV R0.x, c[125].z
DP4 R11.z, R0.yzwx, c[98]
DP4 R11.x, R0.yzwx, c[96]
DP4 R11.y, R0.yzwx, c[97]
DP4 oPos.w, R0.yzwx, c[99]
MOV oPos.xyz, R11
RCC R1.x, R12.w
MUL oPos.xyz, R12.xyz, c[58].xyz
MAD oPos.xyz, R12.xyz, R1.x, c[59].xyz

Some initial values:

v0: 89, 5156, -151, 417
c[58] 320.00, -240.00, 16777215.00, 0.00
c[59] 320.03125, 240.03125, 0.00, 0.00 
c[96] 1.00, 0.00, 0.00, 0.00 
c[97] 0.00, 1.00, 0.00, 0.00 
c[98] 0.00, 0.00, 1.00, 0.00 
c[99] -16.8876857758, -18.2681617737, 3.6487216949, 1.00 
c[120] 1.00, 0.0000610352, 0.0009765625, 1.00
c[125] 1.00, 1.00, 1.00, 0.00 

Some values stepping through with the above inputs (in xemu):

MUL R0.yzw, v0.wxyz, c[120].zx
MOV R0.x, c[125].z

> R0 = 1, 89, 5156, -151

DP4 R11.z, R0.yzwx, c[98]
DP4 R11.x, R0.yzwx, c[96]
DP4 R11.y, R0.yzwx, c[97]

> R11 = 89, 5156, -151, 0

DP4 oPos.w, R0.yzwx, c[99]
MOV oPos.xyz, R11

> oPos = 89, 5156, -151, -96243.6015625

RCC R1.x, R12.w

> R1.x = -0.0000103903

MUL oPos.xyz, R12.xyz, c[58].xyz

> oPos = 28480, -1237440, -2533359360, -96243.6015625

MAD oPos.xyz, R12.xyz, R1.x, c[59].xyz

> oPos = 319.7353210449, 252.8886260986, 26322.3671875,-96243.6015625
abaire commented 2 years ago

Surprisingly, running this test with the same shader and same inputs on HW gives the same unusual values:

Spyvsspymenu

So it would appear that this may not be a problem with the programmable part of the vertex shader but rather with how xemu is processing the negative w coord?

abaire commented 2 years ago

I see unhandled pgraph commands between 0x1e80 and 0x1e90 in the logs but they only happen after some of the draws. They also happen in a potentially interesting sequence; vertex shader constants start loading at 0x60 (96, the start of the typical user-configurable constant range), then 0x1e80,84,88,8c, 90, then constants continue loading at 0x7D (125). The values passed to the 1e80-8c vary, but 1e90 is always called with 0x3C (60).

jackchentwkh commented 2 years ago

I did investigated this game last year with no progress. but your finding are meaningful. pgraph methods 1e80 to 1e8c are NV097_SET_TRANSFORM_DATA and 1e90 is NV097_LAUNCH_TRANSFORM_PROGRAM so clearly these methods are used to transform certain vertices. too bad these methods are not implemented yet. for 1e80~1e8c, they are easy, simple register read and write. 1e90 is the key.

jackchentwkh commented 2 years ago

ok, after a quick reverse engineering and study, here I have some findings.

  1. 0x1e8x NV097_SET_TRANSFORM_DATA is supposed to be an array of four float values that are assigned to the V0 register used in vertex shader.
  2. 0x1e90 NV097_LAUNCH_TRANSFORM_PROGRAM shall launch the vertex shader inside GPU, and the argument comes with it is the starting point (slot) of the vertex shader, which shall be sitting within 0 to 135.
abaire commented 2 years ago

Interesting, though if that's the case I'm now even more confused about what it does...

The transform program that is loaded in the frame is 14 slots, though it's possible that there's a longer program that was loaded earlier during the startup sequence that I didn't capture in my pgraph trace. I suppose it is possible that such a shader could be set up to write to the c registers or it could be populating some output that isn't set by the 14 slot shader above, but the constants used by the 14 slot shader are all explicitly set, and the input values are also explicitly set via NV097_SET_VERTEX_DATA_ARRAY invocations (v0, v1, and v2 are all populated just before the draw).

jackchentwkh commented 2 years ago

The shaders could be loaded independently. User can assign the starting slot dusting each load. The usages of these methods are vertex state shaders. These info are collected by reversing Xbox d3d RunVertexStateShader() And yes, the sample code from xdk for vertex state shader is mean to alter C registers. Vertex state shader is not associate to vertex stream.

abaire commented 2 years ago

Yeah, that's what I assumed as well. I did a more extensive trace (took a bit as I had to write a utility to convert from pgraph trace to nv2a-vsh asm) and it is explicitly loading a context setting program:

Shader at 0x3c (60)
MOV R2.xyzw, c[96]
MOV R3.xyzw, c[97]
MOV R4.xyzw, c[98]
MOV R5.xyzw, c[99]
DP4 c[104].y, R3, c[92]
MUL R8.xyzw, R4, v0.z
MUL R6.xyzw, R2, v0.x
DP4 c[104].x, R2, c[92]
DP4 c[105].x, R2, c[93]
DP4 c[106].x, R2, c[94]
DP4 c[107].x, R2, c[95]
DP4 c[105].y, R3, c[93]
DP4 c[106].y, R3, c[94]
DP4 c[107].y, R3, c[95]
DP4 c[104].z, R4, c[92]
DP4 c[105].z, R4, c[93]
DP4 c[106].z, R4, c[94]
DP4 c[107].z, R4, c[95]
DP4 c[104].w, R5, c[92]
DP4 c[105].w, R5, c[93]
DP4 c[106].w, R5, c[94]
DP4 c[107].w, R5, c[95]
MUL R7.xyzw, R3, v0.y
MUL R9.xyzw, R5, v0.w
DP4 c[100].x, R6, c[92]
DP4 c[101].x, R6, c[93]
DP4 c[102].x, R6, c[94]
DP4 c[103].x, R6, c[95]
DP4 c[100].y, R7, c[92]
DP4 c[101].y, R7, c[93]
DP4 c[102].y, R7, c[94]
DP4 c[103].y, R7, c[95]
DP4 c[100].z, R8, c[92]
DP4 c[101].z, R8, c[93]
DP4 c[102].z, R8, c[94]
DP4 c[103].z, R8, c[95]
DP4 c[100].w, R9, c[92]
DP4 c[101].w, R9, c[93]
DP4 c[102].w, R9, c[94]
DP4 c[103].w, R9, c[95]
DP4 c[96].x, R6, c[88]
DP4 c[97].x, R6, c[89]
DP4 c[98].x, R6, c[90]
DP4 c[99].x, R6, c[91]
DP4 c[96].y, R7, c[88]
DP4 c[97].y, R7, c[89]
DP4 c[98].y, R7, c[90]
DP4 c[99].y, R7, c[91]
DP4 c[96].z, R8, c[88]
DP4 c[97].z, R8, c[89]
DP4 c[98].z, R8, c[90]
DP4 c[99].z, R8, c[91]
DP4 c[96].w, R9, c[88]
DP4 c[97].w, R9, c[89]
DP4 c[98].w, R9, c[90]
DP4 c[99].w, R9, c[91]

Unfortunately this will be non-trivial to implement as we don't have a good way to emulate writable c-registers.

It looks like all of the important registers other than c[125] would be modified by this shader and thus may not match the values being used by xemu. The other constants are all set before the LAUNCH is invoked.

jackchentwkh commented 2 years ago

Damn you’re quick! Hand crafting a code to verify the result is the quickest way I guess.

abaire commented 2 years ago

Confirmed that implementing these two commands fixes the menu.

Screen Shot 2022-06-19 at 15 07 47
jackchentwkh commented 2 years ago

did you press the START and check the in game renderings? the in game renderings uses to have similar issues, should be fixed as well.

abaire commented 2 years ago

I've never actually played the game on HW before and am away from my Xbox for the next few days (I bought it specifically because it was broken in xemu and dumped it), but in-game looks reasonable to me at a glance.

Screen Shot 2022-06-19 at 21 18 17