Skip to content

macOS: present on Retina and scaled displays (#191) - #225

Merged
YuutaTsubasa merged 1 commit into
mainfrom
claude/issue-191-mac-present
Oct 20, 2026
Merged

YuutaTsubasa merged 1 commit into
mainfrom
claude/issue-191-mac-present

Conversation

@YuutaTsubasa

Copy link
Copy Markdown
Owner

Fixes the macOS close right after Start reported in #191 (M1 MacBook Pro, internal display and an external display scaled to 2560x1440).

Cause (reproduced)

The game window on macOS was not high DPI, so SDL's Metal layer had contents scale 1: a 1280x720 window gave a 1280x720 layer. Plume's CocoaWindow sized the swap chain by the window's backing scale (2 on Retina or scaled displays), so it made 2560x1440 swap chains. MoltenVK holds a swap chain to the layer's own size (bounds x contents scale) and returned VK_SUBOPTIMAL_KHR for every present. Plume treats that as a failure on Apple, the game rebuilt the swap chain 8 times at the same wrong size, and then stopped with failed to present native swap-chain texture. The reporter's log shows the same thing: contents scale 1.0, swap chain 2560x1440, 8 present_refused.

Reproduced on our M1 (macOS 27.0.1) with the 0.10.14 release package, run by the harness and through the launcher (Start pressed by the launcher, windowed and full screen settings), on a display in 1280x720 HiDPI mode (2560x1440 pixels). The new logging shows result=VK_SUBOPTIMAL_KHR(1000001003) for each refused present. 1x displays were never affected, which is why our 1920x1080 Mac ran it fine.

Fix

  • native_graphics.cpp: the macOS game window is high DPI (SDL_WINDOW_ALLOW_HIGHDPI), as on iOS (iOS: fix the first present (Issue #185) and draw the launcher's Chinese #194). The layer then takes the screen's scale.
  • patches/plume-present-result-layer-size.patch:
    • On macOS the swap chain takes the size of the window's Metal layer (bounds x contents scale, truncated as MoltenVK does), the same approach as src/plume_apple_ios.mm. The window's backing scale is used only when there is no Metal layer. The swap chain and the layer can no longer disagree, even with a layer that is not high DPI.
    • VulkanSwapChain keeps the VkResult of the last present and acquire.
  • native_presentation.cpp:
    • present_refused and acquire_refused lines in game.log name the VkResult on all platforms (Android's rebuild path too).
    • A present that is still VK_SUBOPTIMAL_KHR after 8 rebuilds is kept instead of closing the game, logged once as NATIVE_PRESENTATION suboptimal_kept. MoltenVK still shows that image, scaled into the layer. A normal present ends this. Other failures still stop the game as before.
  • docs/vulkan.md: a section on this problem.

Windows, Linux and Android behaviour is the same except for the extra log text. Plume already accepted VK_SUBOPTIMAL_KHR there, and its swap-chain sizing there is unchanged. iOS keeps its own Plume glue.

Tests

On the M1, with the release 0.10.14 app bundle and only sfr_cpu_diagnostic swapped for this build:

Display Run 0.10.14 This branch
1280x720 HiDPI (2560x1440 px) harness, short 8 refused, exit 1 0 refused; swap chain 2560x1136, scale 2.0
1280x720 HiDPI harness, full race - 455/455 pipelines, 0 mvk-error, speed 1.00022
1280x720 HiDPI launcher, full-screen setting 8 refused, closed 0 refused
1280x720 HiDPI launcher, 1280x720 windowed 8 refused, closed 0 refused
1280x720 HiDPI harness, SFR_FULLSCREEN=1 8 refused, exit 1 0 refused
960x540 HiDPI (1920x1080 px) harness, short - 0 refused, scale 2.0
2560x1440 1x harness, full race - 455/455, 0 mvk-error, speed 1.00022
2560x1440 1x harness, 2560x1440 window - 0 refused
2560x1440 1x launcher, windowed - 0 refused

These runs also cover each fix on its own:

  • Logging only (old sizing, no high DPI): VK_SUBOPTIMAL_KHR(1000001003) 8 times, then suboptimal_kept, and the game ran on.
  • Plume layer sizing only (no high DPI): 1280x568 swap chain at scale 1, no refusals.
  • Both fixes: 2560x1136 at scale 2, no refusals.

Other checks:

  • iOS: ninja -C out/build/rel-ios sfr_launcher sfr_cpu_diagnostic builds.
  • Windows: ctest 163 passed. native_shader_abi and native_resolution were not built by build_tools.ps1 (CI excludes both).
  • Python: 356 tests OK, 11 skipped.
  • Linux (WSL, clang-15): ctest 159/159.

Not tested

  • Quarantine/AppTranslocation: Gatekeeper's prompt needs the GUI. The reporter's log shows that MoltenVK loaded from the translocated bundle, so translocation is not involved.
  • A real Retina panel or a 4K external display: the tests used a virtual HiDPI display.
  • True full screen: on this headless Mac the window never enters full screen, with 0.10.14 or with this branch.
  • Moving the window between displays with different scales.

A separate, older problem showed up again during testing: GUEST_ACCESS_VIOLATION guest=0x10 in sub_8249C100 at startup. It hit 2 of about 18 harness runs on the M1, once with 0.10.14 and once with this branch, and is not related to this change.

🤖 Generated with Claude Code

On a Retina or scaled display the game closed right after Start with
"failed to present native swap-chain texture". The game window was not
high DPI, so SDL's Metal layer had contents scale 1 (1280x720 pixels),
while Plume sized the swap chain by the window's backing scale (2560x1440).
MoltenVK holds a swap chain to the layer's own size and returned
VK_SUBOPTIMAL_KHR for every present; Plume treats that as a failure on
Apple, and after 8 rebuilds at the same wrong size the game gave up.
Reproduced on the M1 with 0.10.14 (through the launcher too) on a
1280x720 HiDPI display mode; 1x displays were never affected.

- The macOS game window is high DPI, as on iOS: the layer takes the
  screen's scale.
- Plume patch: on macOS the swap chain takes the window's Metal layer
  size (bounds x contents scale, truncated as MoltenVK does), and the
  swap chain keeps the VkResult of the last present and acquire.
- game.log names the VkResult of every refused present and acquire
  (all platforms). A present still suboptimal after 8 rebuilds is kept
  (MoltenVK shows the image scaled) instead of closing the game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 20, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@YuutaTsubasa
YuutaTsubasa merged commit 2faf8e2 into main Oct 20, 2026
4 checks passed
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