Skip to content

Fix FB-passthrough wallpaper upside-down with MSAA > 1#183

Merged
JRickey merged 1 commit into
mainfrom
agent/fb-flip-investigation
May 15, 2026
Merged

Fix FB-passthrough wallpaper upside-down with MSAA > 1#183
JRickey merged 1 commit into
mainfrom
agent/fb-flip-investigation

Conversation

@JRickey
Copy link
Copy Markdown
Owner

@JRickey JRickey commented May 15, 2026

Summary

  • Root cause of "1P stage-clear wallpaper renders upside-down on Linux/OpenGL" turned out to be the MSAA path in port_capture_register_fb_for_subrect, not a GL_TEXTURE_2D sampling-direction quirk as commit 76cda34 had concluded.
  • The bridge was sourcing the GPU→GPU blit from mGameFbMsaaResolved (which the interpreter creates with opengl_invertY=false), while the internal snapshot FB is invertY=true. CopyFramebuffer's invertY-mismatch path applied an unwanted Y-swap during glBlitFramebuffer, leaving the snapshot's storage row 0 holding game-bottom instead of game-top.
  • 1P (top-down v0<v1 stripes) ended up upside-down on screen; VS results photo wipe (bottom-up v0>v1 mesh) coincidentally cancelled the same flip and looked right-side-up. That's why "1P upside-down, VS rightside-up" was the consistent v0.9-beta+MSAA>1 report.

Fix

Always source the blit from mGameFb:

  • mGameFb is invertY=true, matching snap_fbCopyFramebuffer does a clean 1:1 row copy.
  • When MSAA > 1, glBlitFramebuffer performs the MSAA→non-MSAA resolve implicitly during an unscaled blit with matching dimensions (OpenGL spec). The previous two-step "resolve to mGameFbMsaaResolved, then blit" was unnecessary.
  • The legacy ViewportMatchesRendererResolution + MSAA bailout (return -4) is also removed: that case existed because mGameFbMsaaResolved could be stale (end-of-frame resolve targets FB 0 instead). mGameFb itself is always populated mid-frame, so it's a valid blit source regardless of viewport.

Why 76cda34's diagnosis missed this

The May 11 doc's truth table (1P right-side-up + VS right-side-up = V-flip OFF correct) was correct for MSAA=1, which is what the doc author tested. With MSAA>1 (the failing config every Linux/OpenGL user actually saw in v0.9-beta), the truth table doesn't apply because the asymmetry isn't in FbNeedsSampleVFlip — it's in CopyFramebuffer's metadata-mismatch handling.

The 76cda34 commit is not reverted here. Its premise (V-flip OFF is correct on Linux/OpenGL) holds. This PR fixes the actual MSAA bug that was masking that correctness.

Test plan

  • MSAA=1, 1P stage clear photo wallpaper renders right-side-up (unchanged)
  • MSAA=4, 1P stage clear photo wallpaper renders right-side-up (was upside-down)
  • MSAA=1, VS results photo wipe renders right-side-up (unchanged)
  • MSAA=4, VS results photo wipe renders right-side-up (was right-side-up by accident; now right-side-up for the right reason)
  • Mac/Metal smoke test (Metal CopyFramebuffer has its own MSAA-source handling; the fix routes through interp->mRapi->CopyFramebuffer so it should be transparent, but worth a quick check)
  • D3D11/Windows smoke test (same)

Tested locally on Linux/Fedora 43 + NVIDIA 595.58.03.

🤖 Generated with Claude Code

port_capture_register_fb_for_subrect's MSAA branch sourced the blit
from mGameFbMsaaResolved, which the interpreter creates with
opengl_invertY=false (see interpreter.cpp UpdateFramebufferParameters
for mGameFbMsaaResolved). The internal snapshot FB is invertY=true.
CopyFramebuffer sees the invertY mismatch and applies a Y-swap during
the blit, leaving the snapshot's storage row 0 holding game-bottom
instead of game-top.

Effect on consumers:
- 1P stage-clear wallpaper (sc1pstageclear.c, top-down v0<v1 stripes):
  every stripe samples the wrong slice and renders upside-down.
- VS results photo wipe (lbtransition.c, bottom-up v0>v1 mesh): the
  mesh's bottom-up authoring happens to cancel the unwanted Y-flip so
  the photo lands right-side-up by accident.

This is the actual root cause of "1P upside-down on Linux/OpenGL" that
was misdiagnosed as a GL_TEXTURE_2D vendor quirk in commit 76cda34 /
libultraship 423d7a37. The May 11 doc's truth table was correct for
MSAA=1 (both right-side-up with V-flip OFF) but the diagnosis ran on
MSAA>1, observed 1P upside-down, and chased the wrong axis. v0.9-beta
ships with V-flip OFF + MSAA path = upside-down 1P, which is what
Linux users reported.

Fix: source the blit from mGameFb directly. mGameFb is invertY=true
(matches snap_fb) so CopyFramebuffer does a 1:1 row copy with no
hidden Y-flip. When MSAA > 1, mGameFb is multi-sampled — that's fine
because glBlitFramebuffer resolves MSAA→non-MSAA implicitly during an
unscaled blit when dimensions match (per OpenGL spec). The previous
two-step (resolve to mGameFbMsaaResolved, then blit) was unnecessary.

The legacy ViewportMatchesRendererResolution+MSAA bailout is also
removed: it existed because mGameFbMsaaResolved goes stale in that
viewport configuration (end-of-frame resolve targets FB 0 instead).
mGameFb itself is always populated mid-frame, so it's a valid blit
source regardless of viewport.

Tested on Linux/Fedora 43 + NVIDIA 595.58.03:
- MSAA=1, 1P stage clear: right-side-up (unchanged).
- MSAA=4, 1P stage clear: right-side-up (was upside-down).
- MSAA=1, VS results photo wipe: right-side-up (unchanged).
- MSAA=4, VS results photo wipe: right-side-up (was right-side-up by
  accident; now right-side-up for the right reason).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JRickey JRickey merged commit b1a3595 into main May 15, 2026
JRickey added a commit that referenced this pull request May 15, 2026
Resolves PR #182 conflicts after main reverted #179 and merged
#181 + #183 ahead of this branch:

- decomp: cherry-picked PR #181's scene_curr fix (fddd2d3d5) onto
  stability-fixes' tip (4015e25e -> f9d608f11) so the merged tree
  has both the per-slot generational token + DObjDesc PortRefGfx
  work AND the C-Stick / D-Pad input gate fix.
- port/bridge/framebuffer_capture.cpp: kept main's PR #183 MSAA
  fix (the variant 6 / container-smash work doesn't touch this).
- port/port.cpp, port/port_dl_ranges.{cpp,h}, port/port_watchdog.cpp,
  port/resource/RelocPointerTable.{cpp,h}, port/bridge/lbreloc_bridge.cpp:
  restored stability-fixes' versions (re-applies the #179 infrastructure
  that variant 6a/6b builds on).
- libultraship: kept stability-fixes' 7f673a5f (already 3 commits
  ahead of main's c16c03f0 with the GFX walker hooks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JRickey JRickey deleted the agent/fb-flip-investigation branch May 15, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant