Skip to content

Add HDR presentation and restore macOS Game Mode - #1249

Open
sn0wst0rm wants to merge 1 commit into
seedds:mainfrom
sn0wst0rm:pr/mkw-mac-presentation
Open

sn0wst0rm wants to merge 1 commit into
seedds:mainfrom
sn0wst0rm:pr/mkw-mac-presentation

Conversation

@sn0wst0rm

Copy link
Copy Markdown
Contributor

Add HDR presentation on macOS and iOS, with launcher controls for Mario Kart World and Donkey Kong Bananza on both platforms. Negotiate display capability through VI, decode PQ and linear scRGB scanout into extended-linear Rec.2020, and apply content luminance metadata through Core Animation. Preserve SDR presentation and tone-map HDR screenshots to SDR. Bypass the perceptual SDR MetalFX scaler for HDR scanout.

Restore LSSupportsGameMode=true in Mac bundles. iOS already enables Game Mode and retains that configuration. The global opt-out followed an M4 Pro low-clock report; that issue and its per-game workaround remain documented. For MKW 1.8.0 on M5 Max, the queue timestamp and allocator-lifetime fixes in #1245 restored stable 60 fps with Game Mode enabled, without forcing GPU clocks or lowering resolution.

Validation: 178 GPU checks covering HDR luminance, gamut conversion, SDR output, crop handling and both Metal presentation paths; 114 packaging, profile, settings, documentation and shader-chain tests; all 263 iOS host sources compile. GPU tests pass with Metal validation enabled. Guest Bezier tone-curve overrides remain unsupported; Core Animation maps HDR to the display.

Front-ends: interface: expose DM_HDR from the shared settings list on macOS and iOS, and rebuild the host with its updated present shader. Mac bundles again opt into Game Mode.

Present negotiated HDR scanout through a float EDR layer on macOS and iOS, with shared luminance metadata and SDR capture conversion. Retain Game Mode eligibility and the documented per-game workaround for affected Macs.

Cover production Metal presentation paths, transfer conversion, display transitions and launcher settings.
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers ios The iPhone and iPad app (dm/ios) mac The Mac front end and app (dm/mac, front_mac.m) tools Tools and scripts (dm/tools, scripts) docs Documentation game: 0100F2C0115B6000 The Legend of Zelda: Tears of the Kingdom game: 040074A01BF12000 Donkey Kong Bananza game: 0400C3F00006E000 A game profile labels Nov 4, 2026
@elieteyssedou

Copy link
Copy Markdown
Contributor

A data point from an M4 Pro, on this game, for the Game Mode revert.

Setup: MacBook Pro M4 Pro (Mac16,8), 24 GB, macOS 27.0. Mario Kart World 1.8.0, fullscreen, 2026-11-03. Build: main 07ca3efc merged into #900 (02fcb806), so before cb59a672, and without #1245 or this PR.

One run, with Game Mode switched off in Game Overlay while playing:

GPU p50 Frame p50
Game Mode on 36-39 ms 38-45 ms
Game Mode off, at once 8-10 ms 17-19 ms

Limits:

For reference, with the key false: 24 minutes on main 975bb4c1 with #1176 merged locally, DM_PRESENT_LINK=0, held 60.0 fps on average at GPU p50 10.7 ms.

@sn0wst0rm

Copy link
Copy Markdown
Contributor Author

Thanks for sharing these measurements. The slowdown was intermittent on my M5 Max too: some runs stayed at 60 fps, while others entered a persistent ~30 fps state near the rivers. Opening the pause menu restored 60 fps only in the menu; switching focus away from the app and back also restored performance in gameplay.

With the fixes now in #1245, I haven’t reproduced that state in my subsequent tests with Game Mode enabled.

Could you clarify how the slowdown behaved on your M4 Pro? Once triggered, did it persist, and did switching focus away and back recover performance without disabling Game Mode? If you can test with #1245 applied, a comparison with Game Mode on and off would help establish whether the issue remains on M4 Pro.

@elieteyssedou

Copy link
Copy Markdown
Contributor

Thanks. This comes from gamepolicyd's log and my notes at the time; the frame log itself is gone.

  • Did it persist? Game Mode was on from 17:13:58 to 17:17:44, about 3 min 45 s. The GPU p50 of 36-39 ms is from that stretch, and the game felt bad during it. I can't tell when in it the slowdown began, and I have no record of it recovering on its own.
  • Focus: I did not try switching away and back. My run may not separate the two, though: I switched Game Mode off through Game Overlay, and gamepolicyd logged paused at 17:17:44, off at 17:17:45 and Game mode disabled at 17:17:59. Opening the overlay may count as the focus change you describe. GPU p50 was 8-10 ms after that, with Game Mode off.
  • Honor NVN event dependencies across guest queues #1245: I have not run it yet.

maxdavidson pushed a commit to maxdavidson/nxnative that referenced this pull request Nov 5, 2026
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maxdavidson

maxdavidson commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Thanks for this. #1176 covers the same ground (HDR output for Switch 2 games), and your findings here improved it:

  • Format: your note that the SDK reads HDR as on only for format 1 checks out. Bananza's nn::vi::detail::DefaultDisplay::QueryHdrStatus (sdk+0x55bd60) sets on = (format == 1) and returns 0x272 for anything above 1. host/vi: HDR output for Switch 2 games on screens that can show EDR #1176 now cites it, crediting this PR.
  • Screen capability: as with your dm_hdr_query, host/vi: HDR output for Switch 2 games on screens that can show EDR #1176 now answers HDR only while the window's screen can show EDR (potential headroom above 1). It reads that four times a second, so the answer follows the window. Mario Kart World and Donkey Kong Bananza both ask for the status every frame (neither uses the HDR status event). Moving the window to an SDR monitor makes the game switch to its SDR window, and moving it back switches it to HDR again. DM_HDR=2 forces HDR on any screen.
  • System tone mapping: host/vi: HDR output for Switch 2 games on screens that can show EDR #1176 first rolled highlights off in the present. It now uses CAEDRMetadata HDR10 with the game's declared extended range instead (Mario Kart World raises it from 0–1000 to 0–1801 nits at the title). Side by side they looked the same. Present-to-screen time was the same in SDR and HDR, about 49.6 ms p50 windowed, measured from the drawables' presented time.
  • A GPU test: t_present_hdr, modelled on your t_present_hdr.m.

Where #1176 differs:

  • 1.0 in the layer is the 203-nit paper white (the metadata's opticalOutputScale is 203), for PQ and SDR frames alike. So the SDR frames before the game switches have the same white as its HDR UI. Here, SDR frames are at 100 nits and the HDR white at 203.
  • The setting is shown for every Switch 2 game, detected from the SDK's Ounce spec rather than a per-game hdr key, and it's off by default.
  • MetalFX upscaling stays on for HDR frames. It runs on the PQ values, before the decode.
  • RGBA16F (scRGB) windows aren't decoded, since no game we ran makes one.
  • The Game Mode change isn't included, to keep that PR to HDR.

Happy to merge efforts, either by folding anything from here into #1176 or the other way round.

— Claude Opus 5.5, via Claude Code

maxdavidson pushed a commit to maxdavidson/nxnative that referenced this pull request Nov 6, 2026
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxdavidson pushed a commit to maxdavidson/nxnative that referenced this pull request Nov 7, 2026
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maxdavidson pushed a commit to maxdavidson/nxnative that referenced this pull request Nov 10, 2026
The status query answers HDR only while the window's screen can show
EDR, and the game follows the window between screens: Mario Kart World
asks every frame and switches windows. The format is 1, as the SDK's
own QueryHdrStatus sets on = (format == 1) (found in seedds#1249).

The HDR layer carries HDR10 metadata with the game's declared range,
and Core Animation maps highlights into the headroom. The host's own
roll-off is gone: side by side it looked the same and reached the
screen in the same time. Screenshots bring highlights to SDR white
with their hue.

Adds t_present_hdr and DM_PRESENT_SHOWN, a log of when frames reach
the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0100F2C0115B6000 The Legend of Zelda: Tears of the Kingdom game: 0400C3F00006E000 A game profile game: 040074A01BF12000 Donkey Kong Bananza host The Horizon host: SVCs, services, threads, memory (dm/host) ios The iPhone and iPad app (dm/ios) mac The Mac front end and app (dm/mac, front_mac.m) nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants