Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dm/games/0100F2C0115B6000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Game-specific findings. The general host changes they led to are listed under [F
## Found on this game

- [A game file whose extension is in capitals is not found](../../../docs/reference/packing.md#a-game-file-whose-extension-is-in-capitals-is-not-found) (2026-11-04, LNPANDY/NX2Apple#554): its `.NSP` stopped the build on a Mac.
- [macOS Game Mode holds the GPU at its lowest clock](../../../docs/reference/frame-rate.md#macos-game-mode-holds-the-gpu-at-its-lowest-clock) (2026-11-04, #349): the Mac's fall to 30 fps at 338 MHz; a new Mac app opts out, a re-packed one needs the Game Overlay switch once.
- [macOS Game Mode holds the GPU at its lowest clock](../../../docs/reference/frame-rate.md#macos-game-mode-holds-the-gpu-at-its-lowest-clock) (2026-11-04, #349): the reported M4 Pro fall to 30 fps at 338 MHz; affected players can turn Game Mode off per game in Game Overlay. The packer-wide opt-out has been reverted.
- [A regress run stops when another app takes the key window](../../../docs/reference/frame-rate.md#a-regress-run-stops-when-another-app-takes-the-key-window): the route's intermittent "hang" was the game paused out of focus (resolved).
- [Old per-shader cache entries grew a game's build folder with each chain change](../../../docs/reference/shaders.md#old-per-shader-cache-entries-grew-a-games-build-folder-with-each-chain-change) (2026-11-03, #1033): 12 GB of earlier chains' entries in `cas/shader`.
- [Same-pass feedback read from the tile](../../../docs/reference/rendering.md#same-pass-feedback-read-from-the-tile) (2026-11-03): the shrine decals of #737.
Expand Down
2 changes: 2 additions & 0 deletions dm/games/040074A01BF12000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Each step below stopped the boot before its fix:

## Found on this game

- [Negotiated HDR scanout to Apple EDR](../../../docs/reference/rendering.md#negotiated-hdr-scanout-to-apple-edr): the HDR setting is available in both Mac and iOS launchers.

- [A depth clear over the whole target with a partial stencil mask](../../../docs/reference/rendering.md#a-depth-clear-over-the-whole-target-with-a-partial-stencil-mask): 22.8 MB of depth and stencil loaded a frame for a stencil mask of 1 (resolved).
- [No Game language row in an app packed from a NUP or a dump](../../../docs/reference/packing.md#no-game-language-row-in-an-app-packed-from-a-nup-or-a-dump): reported by testers; this folder's dump has no control data.
- Everything under [Boot](#boot-mac) is new: the first Switch 2 game here.
Expand Down
1 change: 1 addition & 0 deletions dm/games/040074A01BF12000/game.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ shader_groups /Shader/=shader /Effect/=effect /UI/=layout
shader_note Donkey Kong Bananza shaders

# ---- Graphics ----
hdr on
# agl's render buffer binding wraps its clears in render conditions (SetRenderEnableConditional(RENDER_IF_NOT_EQUAL) at main+0x12798c before the ClearColor at main+0x1279d4):
# without the key, a clear-only rebinding of the composite target right after the composite draw wiped it before the upscaler read it, and every gameplay frame came out black
render_cond on
Expand Down
9 changes: 9 additions & 0 deletions dm/games/0400C3F00006E000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,12 @@ Update 1.8.0 (v917504, SDK 21.4.0) is not on this Mac. Pull request #1116 ported
- [SDK 19's decoder forms](../../../docs/reference/movies.md#sdk-19s-decoder-forms)
- [A version without one of the profile's modules stops the host at boot](../../../docs/reference/packing.md#a-version-without-one-of-the-profiles-modules-stops-the-host-at-boot)
- 1.8.0, by pull request #1116: [The camera through the graphics system](../../../docs/reference/services.md#the-camera-through-the-graphics-system), [Layer HDR metadata](../../../docs/reference/services.md#layer-hdr-metadata), the SDK 21 forms in [The Joy-Con mouse pointer](../../../docs/reference/services.md#the-joy-con-mouse-pointer) and [GameChat at boot](../../../docs/reference/services.md#gamechat-at-boot)

- [Negotiated HDR scanout to Apple EDR](../../../docs/reference/rendering.md#negotiated-hdr-scanout-to-apple-edr): PQ scanout, luminance metadata and SDR captures.
- [HDR display negotiation](../../../docs/reference/services.md#hdr-display-negotiation): the launcher enables HDR on capable displays.

## Game Mode and queue lifetimes

MKW 1.8.0 could remain at 30 fps after splitting submissions at water event dependencies. The queue fixes in [PR #1245](https://github.com/seedds/nxnative/pull/1245) preserve submission timestamps and reuse bounded command-allocator leases without resetting GPU-owned data. Gameplay testing on M5 Max confirmed sustained 60 fps with Game Mode on, correct water and sharp moving images after those fixes.

The Mac packer restores Game Mode eligibility. The [independent M4 Pro report](../../../docs/reference/frame-rate.md#macos-game-mode-holds-the-gpu-at-its-lowest-clock) remains unresolved; affected users can disable Game Mode per game in Game Overlay.
1 change: 1 addition & 0 deletions dm/games/0400C3F00006E000/game.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ shader_groups /Shader/=shader /Effect/=effect /Lib/=lib /Layout/=layout
shader_note Mario Kart World shaders

# ---- Graphics ----
hdr on
# agl wraps its clears in render conditions on Donkey Kong Bananza and Splatoon Raiders; the same library here, to be confirmed on this game
render_cond on
1 change: 1 addition & 0 deletions dm/games/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ One row per profile; the notes are each profile's `README.md`.
| `shared_font` | Optional, decided by the game files without it (the SDK version the host reads from the `sdk` module, `loader.c` `dm_sdk_major`, and `main`'s imports, `dm_game_imports`): what the shared-font address points at. `bfttf` (SDK 11 and later, when `main` imports `ScalableFontEngineHelper::Decode`) = the 8-byte BFTTF header (with the "already decoded" marker) followed by the TTF, which Mario passes to `Decode`; `ttf` = the TTF itself, which SDK 10 and older games hand straight to `ScalableFontEngine::LoadFont` (Super Mario 3D World + Bowser's Fury: with the header the engine fails with error 403 and the game crashes looking up a glyph; Odyssey, Link's Awakening, Age of Calamity and Three Houses alike) and Unity's player to FreeType (Patrick's Parabox: with the header its Japanese, Korean and Chinese text drew nothing). No profile sets it |
| `device_api` | Optional, decided by the SDK version without it: the NVN API version `major.minor` that `nvnDeviceGetInteger` reports, 55.15 for SDK 11 and later (what Mario requires) and 53.313 for SDK 10 and older (`hle_nvn.c` `nvn_api`). Games check it and skip creating their device on a mismatch: Super Mario 3D World + Bowser's Fury (SDK 10) requires major 53 and minor ≥ 313. No profile sets it. `DM_NVN_API` overrides. Not part of the shader chain version (only `shader_*` keys are) |
| `depth_mode` | `gl` (the default, NVN's near = −w) or `zero` (near = 0): the depth mode the game sets with `nvnDeviceSetDepthMode`, for the pipeline bases made before the game runs: the [pipeline archive made offline](../../docs/development.md#the-pipeline-archive-made-offline) for the iPhone (`main0` or `main0_z` vertex entries; built for a game with `pso_mtl4 on` or with `DM_PSO_GPUS`) and the NSP script's Mac prebuild (`DM_PSO_PREBUILD_DEPTH`). The host itself follows the mode the game sets; a wrong value only costs the device its own prebuild. The heap and address region checks of a provisional or derived profile set it when their boot reaches the game's call; without that call they leave it, as no call means unknown, not GL. Keep the key when the profile moves into `dm/games`: it is no shader input. Great Ace Attorney Chronicles, 13 Sentinels (both releases), Big Brain Academy, Link's Awakening, Engage, ASTRAL CHAIN, Luigi's Mansion 3 and Octopath Traveler 0 set `zero` |
| `hdr` | `on` offers the HDR toggle in the Mac and iOS launchers for a game with HDR scanout; `off` (default) hides it. Actual HDR output still requires the player setting and a capable display. |
| `render_base` | The 3D views' base size, docked then handheld (`1600x900 1280x720`): what `DM_RENDER_SCALE` scales. `settings.py` writes it into the render scale's row of `launcher.json` as `base`, so a front end can show the setting in pixels. Only for a game with a render scale stub, and only a size its note documents |
| `op_mode` | The operation mode the game sees at start: `console` (the default, docked) or `handheld`. Super Mario Galaxy and Captain Toad: Treasure Tracker use `handheld`: docked, their pointer is the Joy-Con gyro, which the host does not have; handheld, it is the touch screen, which the iPhone front end feeds with the touches no gamepad control takes (in the 3D layout, outside the right third of the screen, which is the right stick; `DmGamepad.m`). Handheld also renders at 720p. `DM_OPMODE=0` / `1` overrides, also from `Documents/env.txt` |
| `perf_mode` | `follow` (the default): the performance mode follows the operation mode (Boost docked, Normal handheld); `boost`: Boost whatever the operation mode (`hle_oe.c`), a combination a Switch 1 never reports, so a profile choice. Rhythm Heaven Groove plays handheld with Boost: docked means a TV to the game, which then asks for a latency calibration, and only Boost gives its 1920x1080 draw size (handheld Normal draws 1280x720) |
Expand Down
4 changes: 4 additions & 0 deletions dm/games/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ def header_one(g):
"""the host's game_profile.h: the profile constants the host uses, as macros"""
q = lambda s: '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
mods = g.words('modules')
hdr = g.get('hdr', 'off') # Offer HDR in the launcher for games that support HDR scanout.
if hdr not in ('on', 'off'):
sys.exit(f'{g.id}: hdr must be on or off: {hdr}')
vers = ', '.join('{' + q(b['main']) + ', ' + q(b['display']) + '}' for b in g.builds().values() if 'main' in b)
pi = g.get('present_interval', 'game') # present interval: game = as the game sets it (as on the Switch), display = one frame per display tick
if pi not in ('game', 'display'): sys.exit(f'{g.id}: present_interval must be game or display: {pi}')
Expand Down Expand Up @@ -219,6 +222,7 @@ def header_one(g):
L = [f'// Generated by dm/games/game.py from {src}/game.tsv; do not edit',
'#pragma once',
f'#define DM_GAME_ID {q(g.id)}',
f'#define DM_GAME_HDR {int(hdr == "on")}',
f'#define DM_GAME_NAME {q(g.get("name"))}',
f'#define DM_GAME_PROGRAM_ID 0x{int(g.get("program_id"), 16):016X}ull',
f'#define DM_GAME_MODULES {{{", ".join(map(q, mods))}}}',
Expand Down
1 change: 1 addition & 0 deletions dm/games/settings.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ DM_PSO_PREBUILD toggle - 1 gfxapi:nvn Performance Pipeline prebuild The first st
DM_NVN_ENC_THREAD toggle - 1|DM_GAME_ENC_THREAD gfxapi:nvn Performance Encode thread
DM_FPS_HOLD toggle on/off DM_GAME_FPS_HOLD DM_GAME_FPS_TARGET!=0 Performance Hold frames when late
DM_METAL_HUD toggle - 0 always Performance Performance HUD
DM_HDR toggle - 1 DM_GAME_HDR!=0 Game HDR Requires an HDR-capable display. level=basic
108 changes: 86 additions & 22 deletions dm/gfx/hle_vi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Host: returns host static objects; when hooking up Metal, the native window corresponds to the front end's CAMetalLayer (front_metal_layer()).
#include "../host/hle.h"
#include <stdatomic.h>
#include "nvn/nvn_hdr.h"

static struct { uint32_t magic; } g_display = {0x50534944}, g_layer = {0x5259414c};
static struct { uint32_t magic; void *layer; } g_native = {0x4e49574e, 0};
Expand Down Expand Up @@ -83,13 +84,37 @@ HLE_FORCE("_ZN2nn2vi8FinalizeEv", "display service shutdown; the host has no vi

// ---------- Switch 2 SDKs: HDR, VRR and extended swap intervals ----------
// The SDK's versions check the display against its own default display object and forward the layer to vi2, whose layer object the host never made (Donkey Kong Bananza: a call
// through null from SetLayerNextHdrContentGamutMetadata, main+0x2036f4). The host's output is SDR at a fixed refresh rate: no HDR output, no VRR, and the layer metadata has
// nothing to apply to. HdrOutputFormat 0 is SDR; paper white is the BT.2408 reference, 203 nits, read only when HDR is on
HLE_FORCE("_ZN2nn2vi21QueryDisplayHdrStatusEPbPNS0_15HdrOutputFormatEPtPKNS0_7DisplayE", "display HDR status: SDR output (the host presents SDR)", Result, h_vi_HdrStatus,
uint8_t *on, uint32_t *fmt, uint16_t *peak, const void *disp) {
(void)disp; if (on) *on = 0; if (fmt) *fmt = 0; if (peak) *peak = 0; return RES_OK; }
HLE_FORCE("_ZN2nn2vi25QueryDisplayHdrPaperWhiteEPtPKNS0_7DisplayE", "display HDR paper white: 203 nits (BT.2408); HDR is off", Result, h_vi_PaperWhite, uint16_t *nits, const void *disp) {
(void)disp; if (nits) *nits = 203; return RES_OK; }
// through null from SetLayerNextHdrContentGamutMetadata, main+0x2036f4). HDR is opt-in and requires a capable display and the float EDR present path.
// Bananza's SDK 20.5.19 QueryHdrStatus (sdk+0x55bd60) defines on=(format==1), accepts only formats 0/1. The virtual HDR mastering display is 1000 nits; Core Animation tone maps it to the screen.
HLE_FORCE("_ZN2nn2vi21QueryDisplayHdrStatusEPbPNS0_15HdrOutputFormatEPtPKNS0_7DisplayE",
"display HDR status: EDR capability of the current host screen", Result, h_vi_HdrStatus, uint8_t *on,
uint32_t *fmt, uint16_t *peak, const void *disp) {
(void)disp;
int enabled = dm_hdr_query();
if (on) {
*on = enabled;
}
if (fmt) {
*fmt = enabled ? 1 : 0;
}
if (peak) {
*peak = enabled ? DM_HDR_MASTERING_PEAK : 0;
}
static atomic_int last = -1;
if (atomic_exchange(&last, enabled) != enabled) {
LOG("vi HDR: %s, virtual peak %d nits, paper white %d nits", enabled ? "enabled (EDR display)" : "off",
enabled ? DM_HDR_MASTERING_PEAK : 0, DM_HDR_PAPER_WHITE);
}
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi25QueryDisplayHdrPaperWhiteEPtPKNS0_7DisplayE", "display HDR paper white: 203 nits (BT.2408)",
Result, h_vi_PaperWhite, uint16_t *nits, const void *disp) {
(void)disp;
if (nits) {
*nits = DM_HDR_PAPER_WHITE;
}
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi24QueryDisplayVrrSupportedEPbPKNS0_7DisplayE", "display VRR: not supported (fixed refresh rate)", Result, h_vi_VrrSupported, uint8_t *on, const void *disp) {
(void)disp; if (on) *on = 0; return RES_OK; }
HLE_FORCE("_ZN2nn2vi23SetDisplayVrrCapabilityEPNS0_7DisplayEb", "display VRR capability: the host has no VRR", Result, h_vi_SetVrr, void *disp, unsigned on) {
Expand All @@ -115,18 +140,57 @@ HLE_FORCE("_ZN2nn2vi28SetLayerExtendedSwapIntervalEPNS0_5LayerEii", "layer exten
void *layer, int a, int b) {
(void)layer; static atomic_int once; if (!atomic_exchange(&once, 1)) LOG("vi::SetLayerExtendedSwapInterval %d %d: no VRR on the host, the NVN swap interval stays", a, b); return RES_OK; }
HLE_FORCE("_ZN2nn2vi30ClearLayerExtendedSwapIntervalEPNS0_5LayerE", "layer extended swap interval cleared", Result, h_vi_ExtSwapClear, void *layer) { (void)layer; return RES_OK; }
HLE_FORCE("_ZN2nn2vi35SetLayerNextHdrContentGamutMetadataEPNS0_5LayerENS0_15HdrContentGamutE", "layer HDR metadata: SDR output, nothing to apply", Result, h_vi_HdrGamut, void *layer, int g) {
(void)layer; (void)g; return RES_OK; }
HLE_FORCE("_ZN2nn2vi39SetLayerNextHdrToSdrToneMappingMetadataEPNS0_5LayerENS0_14HdrToneMappingE", "layer HDR metadata: SDR output, nothing to apply", Result, h_vi_HdrToneMap, void *layer, int m) {
(void)layer; (void)m; return RES_OK; }
HLE_FORCE("_ZN2nn2vi52SetLayerNextHdrExtendedContentLuminanceRangeMetadataEPNS0_5LayerEtt", "layer HDR metadata: SDR output, nothing to apply", Result, h_vi_HdrExtLum,
void *layer, uint32_t lo, uint32_t hi) { (void)layer; (void)lo; (void)hi; return RES_OK; }
HLE_FORCE("_ZN2nn2vi52SetLayerNextHdrStandardContentLuminanceRangeMetadataEPNS0_5LayerEtt", "layer HDR metadata: SDR output, nothing to apply", Result, h_vi_HdrStdLum,
void *layer, uint32_t lo, uint32_t hi) { (void)layer; (void)lo; (void)hi; return RES_OK; }
// The rest of SDK 22's layer HDR metadata calls (the Bezier tone curve and the clears): the SDK forwards them through a table its graphics system fills, which is null on the host, so
// unanswered they call through null (Mario Kart World 1.8.0 sets the curve every frame, by pull request #1116)
HLE_FORCE("_ZN2nn2vi39SetLayerNextHdrAdjustableBezierMetadataEPNS0_5LayerEfff", "layer HDR metadata: SDR output, nothing to apply", Result, h_vi_HdrBezier,
void *layer, float a, float b, float c) { (void)layer; (void)a; (void)b; (void)c; return RES_OK; }
HLE_FORCE("_ZN2nn2vi41ClearLayerNextHdrAdjustableBezierMetadataEPNS0_5LayerE", "layer HDR metadata cleared: SDR output", Result, h_vi_HdrBezierClear, void *layer) { (void)layer; return RES_OK; }
HLE_FORCE("_ZN2nn2vi37ClearLayerNextHdrContentGamutMetadataEPNS0_5LayerE", "layer HDR metadata cleared: SDR output", Result, h_vi_HdrGamutClear, void *layer) { (void)layer; return RES_OK; }
HLE_FORCE("_ZN2nn2vi41ClearLayerNextHdrToSdrToneMappingMetadataEPNS0_5LayerE", "layer HDR metadata cleared: SDR output", Result, h_vi_HdrToneMapClear, void *layer) { (void)layer; return RES_OK; }
HLE_FORCE("_ZN2nn2vi35SetLayerNextHdrContentGamutMetadataEPNS0_5LayerENS0_15HdrContentGamutE",
"layer HDR content gamut", Result, h_vi_HdrGamut, void *layer, int g) {
(void)layer;
dm_hdr_gamut(g);
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi39SetLayerNextHdrToSdrToneMappingMetadataEPNS0_5LayerENS0_14HdrToneMappingE",
"layer HDR-to-SDR policy: Core Animation maps the HDR mastering display to the output screen", Result,
h_vi_HdrToneMap, void *layer, int m) {
(void)layer;
(void)m;
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi52SetLayerNextHdrExtendedContentLuminanceRangeMetadataEPNS0_5LayerEtt",
"layer HDR extended luminance range", Result, h_vi_HdrExtLum, void *layer, uint32_t lo, uint32_t hi) {
(void)layer;
dm_hdr_luminance(1, (uint16_t)lo, (uint16_t)hi);
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi52SetLayerNextHdrStandardContentLuminanceRangeMetadataEPNS0_5LayerEtt",
"layer HDR standard luminance range", Result, h_vi_HdrStdLum, void *layer, uint32_t lo, uint32_t hi) {
(void)layer;
dm_hdr_luminance(0, (uint16_t)lo, (uint16_t)hi);
return RES_OK;
}
// Core Animation supplies the output tone curve; guest Bezier and HDR-to-SDR policy overrides are not applied.
// These hooks still intercept SDK forwarding through the unavailable vi2 layer.
HLE_FORCE("_ZN2nn2vi39SetLayerNextHdrAdjustableBezierMetadataEPNS0_5LayerEfff",
"layer HDR Bezier policy: Core Animation supplies the output tone curve", Result, h_vi_HdrBezier, void *layer,
float a, float b, float c) {
(void)layer;
(void)a;
(void)b;
(void)c;
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi41ClearLayerNextHdrAdjustableBezierMetadataEPNS0_5LayerE",
"layer HDR Bezier policy cleared: Core Animation supplies the output tone curve", Result, h_vi_HdrBezierClear,
void *layer) {
(void)layer;
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi37ClearLayerNextHdrContentGamutMetadataEPNS0_5LayerE", "layer HDR content gamut cleared", Result,
h_vi_HdrGamutClear, void *layer) {
(void)layer;
dm_hdr_gamut(-1);
return RES_OK;
}
HLE_FORCE("_ZN2nn2vi41ClearLayerNextHdrToSdrToneMappingMetadataEPNS0_5LayerE",
"layer HDR-to-SDR policy cleared: Core Animation maps the output", Result, h_vi_HdrToneMapClear,
void *layer) {
(void)layer;
return RES_OK;
}
Loading
Loading