Skip to content

Framebuffer-as-texture passthrough for 1P stage clear + VS results (#81)#154

Merged
JRickey merged 1 commit intomainfrom
agent/vs-fb-transition
May 8, 2026
Merged

Framebuffer-as-texture passthrough for 1P stage clear + VS results (#81)#154
JRickey merged 1 commit intomainfrom
agent/vs-fb-transition

Conversation

@JRickey
Copy link
Copy Markdown
Owner

@JRickey JRickey commented May 8, 2026

Summary

  • Fixes Frame buffer capture for VS match -> results screen transition missing #81 (VS match → results-screen photo wipe rendered black) and supersedes the older CPU-readback fix for the 1P stage-clear wallpaper (issue Frame buffer visuals missing - only black #57) with the same elegant GPU-passthrough path.
  • libultraship gains a generalized FB-texture-passthrough API (RegisterFbTexture(base, sizeBytes, fbId, u0, v0, u1, v1)) that handles N64's universal multi-tile sampling pattern (TMEM is 4 KB so any FB-sized region must be tiled into N LoadBlock cycles). The hook derives the per-stripe UV slice automatically from the offset + the prior LoadBlock's line_size_bytes / size_bytes metadata.
  • Capture happens at mCurDimensions (full GPU resolution) instead of being downsampled to native 320×240, with no per-pixel byteswap or texture-cache eviction.

What changed

libultraship (submodule bump → JRickey/libultraship#ssb64@a51ece10)

  • Interpreter::RegisterFbTexture(base, sizeBytes, fbId, u0, v0, u1, v1) + UnregisterFbTexture + ClearFbTextures. Sub-rect (rather than 1:1) substitution generalizes Kenix3#1046.
  • ImportTexture FB-mirror hook: range lookup, dynamic per-stripe UV slice computation, stash in mFbUvTransform[i]. Reset to identity on miss so a previous hit's slice never leaks.
  • GfxSpTri1 applies the per-tile-slot UV transform when emitting per-vertex normalized UVs and the clamp-coord pair into the VBO. Identity = no behavior change for non-FB textures (every other draw in the suite).
  • gfx_metal.cpp::CopyFramebuffer: standalone path for between-frames callers — EndFrame nulls every FB's command buffer/encoder, so the existing path silently no-op'd when invoked from a game-thread task. Standalone allocates its own command buffer + blit encoder + waitUntilCompleted, modeled after ReadFramebufferToCPU.

decomp (submodule bump → JRickey/ssb-decomp-re#port-patches@73d6c0d07)

  • sc1PStageClearCopyFramebufToWallpaper: replaces the per-pixel memcpy + byteswap + portTextureCacheDeleteRange with a per-stripe port_capture_register_fb_for_subrect loop over the wallpaper sprite's bitmap array.
  • lbTransitionSetupTransition: replaces the bottom-up gSYSchedulerCurrentFramebuffer memcpy with one register_fb_for_subrect(photo_heap, 132 KB, ...) covering the 300×220 photo region; libultraship slices the 44 5-row LoadBlocks the runtime sprite renderer emits.

port (this PR)

  • port/bridge/framebuffer_capture.{h,cpp} rewritten around snapshot-FB + register-range model. Old CPU-readback API (port_capture_game_framebuffer / port_get_captured_framebuffer) removed — no remaining consumers.
  • port/bridge/lbreloc_bridge.cpp::lbRelocInitSetup: hook port_capture_release_all so bump-reset heap reuse can't leave stale pointer-keyed registrations aliasing fresh assets onto the snapshot FB. Same shape as portResetStructFixups / portResetPackedDisplayListCache.

Test plan

  • Build green (Debug, macOS Metal)
  • 1P classic mode → stage clear: wallpaper shows the captured frame correctly across all 37 sprite stripes
  • VS match → results-screen photo wipe: captured frame correctly sliced across all 44 photo-heap stripes; verified across multiple transitions (the picker is syUtilsRandIntRange(11))
  • Cross-backend: macOS verified; Windows D3D11 / Linux GL not yet smoke-tested by reviewer

Closes #81.

🤖 Generated with Claude Code

SSB64 uses an N64 hardware-RDP idiom on the 1P stage-clear screen and
the VS match -> results-screen photo wipe: memcpy from
gSYSchedulerCurrentFramebuffer into a CPU buffer (a wallpaper sprite
or the segment-1 photo heap) and sample it as a texture by a
downstream sprite/mesh. On the port the GPU rasterizer never writes
to gSYSchedulerCurrentFramebuffer, so both copies read zeros and the
result is a solid-black photo. Issue #81 was the open VS-side bug;
issue #57 was the earlier 1P-side fix done via CPU readback +
downsample + byteswap into the wallpaper bitmap.

Replaces both with a single GPU passthrough: register the consumer's
CPU range as a mirror of an internal snapshot FB (mGameFb -> snapshot
via libultraship's CopyFramebuffer once at capture time), and let
Fast3D's ImportTexture bind the snapshot FB directly via
SelectTextureFb when the consumer's gsDPSetTextureImage hits the
registered range -- no CPU readback, no per-pixel byteswap, no
texture-cache eviction, full mCurDimensions resolution.

Components:

* libultraship (submodule bump): RegisterFbTexture sub-rect API +
  per-stripe UV slicing in the ImportTexture hook + per-tile-slot UV
  transform applied in GfxSpTri1's VBO emit + Metal CopyFramebuffer
  standalone path for between-frames calls. Generalizes Kenix3#1046
  (which only handled single-tile substitution) to the common case
  of N>=1 LoadBlock cycles required by N64's 4 KB TMEM.

* decomp (submodule bump): replaces the gSYSchedulerCurrentFramebuffer
  memcpy in sc1pstageclear.c (per-stripe registration of the
  wallpaper sprite's bitmap array) and lbtransition.c (single-range
  registration of the 300x220 photo heap; libultraship slices the 44
  5-row LoadBlocks the runtime sprite renderer emits).

* port/bridge/framebuffer_capture.{h,cpp}: rewrite around the new
  snapshot-FB + register-range model. port_capture_set_force_render_to_fb
  unchanged; old port_capture_game_framebuffer / port_get_captured_framebuffer
  CPU-readback API removed (no remaining consumers).

* port/bridge/lbreloc_bridge.cpp: hook port_capture_release_all into
  lbRelocInitSetup so bump-reset heap reuse can't leave stale
  pointer-keyed registrations aliasing fresh assets onto the snapshot
  FB. Same shape as portResetStructFixups /
  portResetPackedDisplayListCache.

Closes #81. Supersedes the issue #57 CPU-readback fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Frame buffer capture for VS match -> results screen transition missing

1 participant