Skip to content

fix: the capture button saves the game's own frame, not the letterboxed window picture - #2168

Merged
ly05010419 merged 1 commit into
ly05010419:mainfrom
heyseere:fix/screenshot-game-frame
Nov 14, 2026
Merged

ly05010419 merged 1 commit into
ly05010419:mainfrom
heyseere:fix/screenshot-game-frame

Conversation

@heyseere

Copy link
Copy Markdown
Contributor

Problem

The capture button saves a copy of the presented drawable — the window content in pixels. That includes the letterbox bars around the game picture, and it caps the picture at the window's resolution even when the game renders internally at a higher one (the present's MetalFX upscale then downsamples into the window).

Change

The capture button now asks the renderer for the game's own frame. A new mtl_grab_next_game_frame (next to the diagnostics' mtl_grab_next_present, same guard) makes the next present copy the game's picture — the present's crop region of the source texture, that is the output resolution, before the MetalFX output upscale — into a shared buffer in the same command buffer, and the commit feedback handler hands out the CGImage at that resolution: no letterbox bars, no window cap, none of the present-side boxes. The source canvas can be larger than the picture (a 2560x1440 canvas with 1920x1080 output); its remainder is undefined fill, so the copy takes the crop region.

  • An HDR or unexpected source format, or a read-back buffer that cannot be made, falls back to today's drawable copy (an HDR drawable is already tone-pressed to SDR).
  • wshot / TOTK_WINDOW_SHOT keep capturing the window picture; TOTK_WSHOT_SRC still pairs it with its source.
  • The front ends try the game-frame grab first and fall back to the window grab when a build links only that (weak imports; probes without a renderer answer no picture as before).
  • iPhone / iPad receive the full game frame in the photo library instead of the letterboxed screen shape.
  • The OoT tree gets the same change (its nvn_mtl.m is the shared hard-linked graphics layer; its front ends and screenshot.h are wired identically).
  • docs/controls.md (+ zh-CN) states the saved picture is the game's own frame at its render resolution, without the bars.

Tests

  • make check-c passes (12 C tests, screenshot: all checks pass for both variants).
  • make -C games/totk runtime links build/totk-native with the frontend compiled -Werror (the run then stops at the local untracked analysis/phase12-validation.json data target, unchanged from main — not in the repository).
  • make check: 1493 tests, 2 failures, 17 skipped — both reproduce on a pristine checkout of this PR's base (70099c1f): test_one_folder_rule.DefaultWork.test_apps_in_a_folder_whose_parent_cannot_be_written_go_to_the_home_folder (reads the machine's real /Applications plists; environment-dependent) and test_oot_ios_mip_drop.OotIosMipDrop.test_ios_keeps_few_pipeline_archives_open (the source-content assertion behind nvn_archive_map.h changes).
  • Real-game verification (TOTK 1.4.3 on this machine; sandboxed environment with all writes redirected to /tmp, title screen only, app bundle and player data verified byte-identical before/after): in fullscreen (2940x1846 drawable) the capture saved 1920x1080 PNGs — the crop region, not the canvas, not the drawable — with zero undefined-fill pixels and no fallback lines. Before the crop handling, the same test captured the whole 2560x1440 canvas including magenta fill, which is what motivated it.

Honest note on CI: upstream main is red on its own CI — run 43441200958 (70099c1f, this PR's exact base): Source tests (settings) fails 4 × test_current_modes_and_refresh (AssertionError: -6 != 0 : [settings] the contributors were not read: Could not connect to the server.), Source tests (core) fails 1 × test_ios_keeps_few_pipeline_archives_open (the pipeline-archive source-content assertion). None of that red comes from this PR; the test job is the workflow's needs-gated aggregate.

Known limitation: an HDR source frame still captures the letterboxed drawable (tone-pressed to SDR) — a follow-up can read the HDR source directly.

…ed window picture

The controller's capture button (screenshot.h) copied the presented drawable: the
window content, letterbox bars included, at the window's resolution. It now asks the
renderer for the game's own frame (mtl_grab_next_game_frame, next to the diagnostics'
mtl_grab_next_present): at the next present the game's picture -- the present's crop
region of the source texture, that is the output resolution, before the MetalFX output
upscale -- is copied into a shared buffer, and the commit feedback handler hands out
the CGImage at that resolution; the source canvas can be larger than the picture (a
2560x1440 canvas with 1920x1080 output) and its remainder is undefined fill. An HDR or
unexpected source frame, or a read-back buffer that cannot be made, falls back to the
drawable copy (which tone-presses an HDR drawable to SDR); a present that cannot draw
answers NULL like the window grab does. wshot and TOTK_WINDOW_SHOT keep the window
picture, so TOTK_WSHOT_SRC still compares the 2K output with its source. The front
ends call the game-frame grab first and the window grab when a build links only that.
docs/controls.md says the saved picture is the game's own frame; the OoT copy is the
same (the graphics layer is shared, the front ends and screenshot.h are identical).
@ly05010419
ly05010419 merged commit 622e158 into ly05010419:main Nov 14, 2026
1 of 4 checks passed

Copy link
Copy Markdown
Owner

已合并并推送,补充修正见 cb6dac11。

  • 游戏帧使用原始 src/crop 配对,避免将 VR 放大后的裁切用于原始贴图;无效区域明确回退,不再复制未定义填充。
  • 请求摘下后,nogpu、present-skip 和 nextDrawable=nil 路径会交回结果,避免请求丢失;回调在锁外完成。
  • 直接执行生产队列/早退代码的 ASan/UBSan 回归通过,覆盖失败回调一次完成、插帧不消费请求、裁切边界及整数溢出。Mac/iOS 图形源编译通过。

HDR 源仍采用 drawable 回退。本轮未重做窗口/全屏或 HDR 的实际截图验收。谢谢贡献。

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.

2 participants