Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Halo 2: Broken split screen #1986

Open
mborgerson opened this issue Mar 10, 2025 · 7 comments
Open

Halo 2: Broken split screen #1986

mborgerson opened this issue Mar 10, 2025 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@mborgerson
Copy link
Member

Title

https://xemu.app/titles/4d530064/#Halo-2

Bug Description

Player 2 screen has missing geometry.

Image

Expected Behavior

Should appear similar to Player 1's screen.

xemu Version

v0.8.33

System Information

CPU:          
OS Platform:  Linux
OS Version:   Ubuntu 24.04.1 LTS
Manufacturer: NVIDIA Corporation
GPU Model:    NVIDIA GeForce RTX 4090/PCIe/SSE2
Driver:       4.0.0 NVIDIA 535.183.01
Shader:       4.00 NVIDIA via Cg compiler

Additional Context

No response

@mborgerson mborgerson added the bug Something isn't working label Mar 10, 2025
@mborgerson mborgerson self-assigned this Mar 10, 2025
@lostromb
Copy link

My uneducated guess is that this is related to the state of the object culling frustrum during viewport changes. From the screenshots and the behavior I've seen playing locally, you can see that large static geometry appears correctly on the P2 view, but more dynamic parts of the terrain and other players are invisible. Turning the camera in different angles sometimes reveals this hidden geometry - like I can sometimes see a contiguous part of the level appearing normally, just not where player 2 is actually at. My hope is that this is a case where the object culling frustrum is not getting reset when it should, so it's carrying the transform from P1's viewport into P2's and putting it in some weird arbitrary place.

@lostromb
Copy link

Of course it's probably not nearly as simple as that. It's hard to figure out any patterns or clues based on what happens during gameplay. I can say for sure that P1's camera angle or position has no effect on P2's view, so that kind of puts a hole into the redundant transform theory. Unless the bug was that there's some other culling state that's being carried over between viewports other than the frustrum transform. Only other observation is that P2's view generally seems to hide all nearby dynamic objects but show ones that are farther away, so almost the inverse of what you might expect. No clue v0v

@mborgerson
Copy link
Member Author

@lostromb if you're interested you're welcome to debug the issue and send a pr!

@lostromb
Copy link

lostromb commented Mar 11, 2025

Well, I do know the basics of Renderdoc, so I can contribute some diagnosis. It's definitely scissor + depth test related and not any of the other stuff I mentioned before.

The render pipeline for split screen looks roughly like this:
PLAYER 1 VIEWPORT:

  • Do a prepass of "lit" objects to an offscreen framebuffer. Write depth + albedo color for floor terrain, rocks, etc. that will be lit later
  • Switch to primary framebuffer, set viewport and scissor to top half of screen
  • Do a full pass of "static" objects onto the main FB (far terrain, certain structures, skybox, etc.)
  • Do another pass of lit objects and use the information from the prepass to do lighting, compositing it all onto the main FB
  • Draw UI, hand model, etc.

PLAYER 2 VIEWPORT:

  • Do lit object prepass on the main framebuffer <--- These are the objects that don't end up showing on screen by the end
  • Set viewport and scissor to bottom half of screen
  • Do static object pass on the main framebuffer followed by lit object pass 2
  • Draw UI etc.

The main difference I can see when it's processing the viewports is that for P1 it's doing the lit object prepass onto a dedicated 640x240 offscreen buffer, which is then composited later into the "main" framebuffer on the second pass. Whereas in the P2 viewport, the initial depth pass appears to happen on the final composite framebuffer.

On the P2 lighting prepass, the scissor region is still set to the top half of the screen rather than the bottom half, which means depth never gets written to the final depth buffer.

Image
The first command of this color pass is a glClear with the scissor set correctly to the lower half of the main framebuffer, so it properly clears that portion of the screen. Then, the very next command is glDrawElements with scissor set incorrectly to the upper half (0,240 640x240), and it remains that way for the rest of the pass.

On the final composite pass, all the ground terrain draws fail the depth test and get treated as skybox instead. The scissor rectangle is properly set to the lower half of the screen by the time this call comes around.
Image

@lostromb
Copy link

Here's maybe a clearer renderdoc capture with 4-way splitscreen: https://1fichier.com/?dulkcvvm9j7sro0qwwf0

And the same rock being drawn from four different perspectives, in two passes each time. You can see how the stencil coords get changed just before the prepass draw call

Image

Image

@lostromb
Copy link

lostromb commented Mar 12, 2025

Man these images are big. Wish there was a thumbnail option on Github....
Anyway, getting down to the debugger and the pgraph registers for each draw.

Here's the inputs to this draw call (Player 2 / Top Right pre-pass) - this is the draw where clip rect is incorrect:

Image
Image

And for the second pass, this draw call:

Image
Image

There's some kind of disagreement with the clip coords between surface_shape and surface_binding_dim...

@lostromb
Copy link

Well, I....... got it working?????? Maybe???? #1998 Dumb hack that happens to fix Halo 2 splitscreen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants